Khronos keeps every WebGL extension in the WebGL Extension Registry (registry.khronos.org/webgl/extensions (https://registry.khronos.org/webgl/extensions/ 21,380 )), where each moves through stages. Proposed extensions are for discussion and must not be implemented; draft ones may ship behind a flag or prefix; community approved ones ship unprefixed; Khronos ratified ones have also been voted on by Khronos's Board of Promoters; rejected ones must never ship. In September 2026 the registry listed 18 ratified, 35 community approved, 4 draft, 5 proposed and 14 rejected extensions. The drafts include WEBGL_shader_pixel_local_storage and WEBGL_draw_instanced_base_vertex_base_instance; the proposals include WEBGL_video_texture and WEBGL_webcodecs_video_frame. Name prefixes tell the origin: OES from OpenGL ES, EXT multivendor, WEBGL web-specific, KHR Khronos, NV NVIDIA and OVR Oculus. What a browser offers depends on the GPU beneath it:
const names = gl.getSupportedExtensions(), groups = {};
for (const name of names) (groups[name.split('_')[0]] ??= []).push(name);
console.log(names.length, 'extensions |',
Object.entries(groups).map(([prefix, list]) => `${prefix} ${list.length}`).join(', '));32 extensions | EXT 14, KHR 1, NV 1, OES 4, OVR 1, WEBGL 11
That is the GTX 1650; SwiftShader 2,546 printed 30 extensions | EXT 12, NV 1, OES 4, OVR 1, WEBGL 12. Both offered these 27: EXT_clip_control, EXT_color_buffer_float, EXT_color_buffer_half_float, EXT_conservative_depth, EXT_depth_clamp, EXT_disjoint_timer_query_webgl2, EXT_float_blend, EXT_polygon_offset_clamp, EXT_texture_compression_bptc, EXT_texture_compression_rgtc, EXT_texture_filter_anisotropic, EXT_texture_mirror_clamp_to_edge, NV_shader_noperspective_interpolation, OES_draw_buffers_indexed, OES_sample_variables, OES_shader_multisample_interpolation, OES_texture_float_linear, OVR_multiview2, WEBGL_clip_cull_distance, WEBGL_compressed_texture_s3tc, WEBGL_compressed_texture_s3tc_srgb, WEBGL_debug_renderer_info, WEBGL_debug_shaders, WEBGL_lose_context, WEBGL_multi_draw, WEBGL_polygon_mode and WEBGL_stencil_texturing. Only the GTX 1650 added EXT_render_snorm, EXT_texture_norm16, KHR_parallel_shader_compile, WEBGL_blend_func_extended and WEBGL_provoking_vertex; only SwiftShader added WEBGL_compressed_texture_astc, _etc and _etc1.
<!doctype html>
<style>
body { margin: 0; font: 11px system-ui, sans-serif; background: #f7f4ee; color: #333; }
.wrap { position: relative; max-width: 600px; }
canvas { display: block; width: 100%; }
.lbl { position: absolute; transform: translate(-50%, 0); font: 10px monospace; text-align: center; white-space: nowrap; }
pre { max-width: 600px; margin: 4px 0; font: 10px monospace; white-space: pre-wrap; }
</style>
<div class="wrap"><canvas id="c" width="600" height="260"></canvas></div>
<pre id="out"></pre>
<script>
const gl = document.getElementById('c').getContext('webgl2');
const names = gl.getSupportedExtensions(), groups = {};
for (const name of names) (groups[name.split('_')[0]] ??= []).push(name); // OES, EXT, WEBGL, KHR, NV, OVR...
const prefixes = Object.entries(groups).sort((a, b) => b[1].length - a[1].length);
// The registry in September 2026, by stage.
const stages = [['ratified', 18, [0.25, 0.49, 0.23]], ['community', 35, [0.12, 0.37, 0.55]], ['draft', 4, [0.88, 0.6, 0.06]],
['proposed', 5, [0.36, 0.25, 0.6]], ['rejected', 14, [0.71, 0.27, 0.18]]];
const wrap = document.querySelector('.wrap');
gl.clearColor(0.97, 0.96, 0.93, 1); gl.clear(gl.COLOR_BUFFER_BIT);
gl.enable(gl.SCISSOR_TEST);
function bars(list, x0, width, max, title) { // vertical bars from scissored clears
const w = width / list.length;
list.forEach(([label, count, color], i) => {
const h = count / max * 190;
gl.scissor(Math.round(x0 + i * w + 4), 40, Math.round(w - 8), Math.max(2, Math.round(h)));
gl.clearColor(...color, 1); gl.clear(gl.COLOR_BUFFER_BIT);
const cx = (x0 + (i + 0.5) * w) / 6;
wrap.insertAdjacentHTML('beforeend', `<div class="lbl" style="left:${cx}%;top:${(260 - 40 - h - 14) / 2.6}%">${count}</div>` +
`<div class="lbl" style="left:${cx}%;top:${(260 - 36) / 2.6}%">${label}</div>`);
});
wrap.insertAdjacentHTML('beforeend', `<div class="lbl" style="left:${(x0 + width / 2) / 6}%;top:92%;font-weight:bold">${title}</div>`);
}
const palette = [[0.12, 0.37, 0.55], [0.16, 0.62, 0.56], [0.36, 0.25, 0.6], [0.88, 0.6, 0.06], [0.71, 0.27, 0.18], [0.25, 0.49, 0.23]];
bars(prefixes.map(([p, list], i) => [p, list.length, palette[i % palette.length]]), 10, 280, Math.max(...prefixes.map(([, l]) => l.length)), `this browser: ${names.length} extensions`);
bars(stages, 310, 280, 35, 'the registry, by stage');
document.getElementById('out').textContent = prefixes.map(([p, list]) => `${p}: ${list.join(', ')}`).join('\n');
</script>