THREE.WebGLRenderer: A WebGL context could not be created. Reason: Canvas has an existing context of a different type

What three.js means by "THREE.WebGLRenderer: A WebGL context could not be created. Reason:", the event listener in WebGLRenderer.js that prints the browser's reason, what each kind of reason points to, and how a coding agent catches a second context request on three's canvas with the paprika3d plugin.

This console error is three.js passing on a message from the browser. The text after Reason: is written by Chrome or Firefox, not by three, and it is the part worth reading: it says whether the browser refused WebGL on this machine or whether a page asked a canvas for a second, different context. The reason in the heading is the one this page reproduces; the double space after Reason: is how it prints.

Where three.js prints it

src/renderers/WebGLRenderer.js (three r186) adds a listener for the canvas event webglcontextcreationerror in its constructor, before it asks for a context, and the handler logs the event's statusMessage:

// event listeners must be registered before WebGL context is created, see #12753
canvas.addEventListener( 'webglcontextlost', onContextLost, false );
canvas.addEventListener( 'webglcontextrestored', onContextRestore, false );
canvas.addEventListener( 'webglcontextcreationerror', onContextCreationError, false );
function onContextCreationError( event ) {

	error( 'WebGLRenderer: A WebGL context could not be created. Reason: ', event.statusMessage );

}

three's error() adds THREE. and calls console.error with two arguments, so the console joins the text, which already ends in a space, and the reason with one more space. The browser fires the event whenever a getContext call for a WebGL name fails on that canvas. Two moments matter:

Why the browser refused

The reason text sorts the cases:

A page that reproduces it

This page renders a cube, and reads the GPU's name for a bug report the WebGL 1 way, from the renderer's own canvas:

const renderer = new THREE.WebGLRenderer({ antialias: true });
// ...
const gl = renderer.domElement.getContext('webgl'); // three's canvas already holds a webgl2 context
const ext = gl && gl.getExtension('WEBGL_debug_renderer_info');
window.gpuName = ext ? gl.getParameter(ext.UNMASKED_RENDERER_WEBGL) : 'unknown';

The replies below are from paprika3d, a Claude Code plugin and MCP server that drives three.js pages in its own headless Chrome. They were captured with its stable build on 2026-10-28 on a machine with no GPU, cut to the lines this page explains, with no line changed and the project path written as <project>.

open {"url":"index.html","cwd":"<project>"}

Opened http://127.0.0.1:35159/index.html in 0.3 s, paused at frame 0.
Page text outside the canvas: none
{"three":"186","scenes":1,"entities":["cube"]}
Errors so far:
THREE.WebGLRenderer: A WebGL context could not be created. Reason:  Canvas has an existing context of a different type

look {}

On screen (1):
cube 5.8% [518, 284, 761, 537]
Never visible: none

eval {"code":"return window.gpuName"}

unknown

open lists the line under Errors so far, because the request ran while the page set up, before its first frame. look still lists cube on screen: three's WebGL 2 context was never touched, so the scene draws and the error looks harmless. eval shows the real damage: getContext('webgl') returned null, the guard swallowed it, and the bug report would say unknown. A support check written the same way, if (!canvas.getContext('webgl')), would show a "WebGL is not available" notice on a page that renders fine.

The replies cannot show the GPU-side reasons. Chrome on this machine draws with SwiftShader, which accepts every request, so a blocked driver's reason text is not something these calls reproduce.

The fix

Ask the renderer for the context it already made. renderer.getContext() returns three's WebGL 2 context, and the debug extension works on it:

<!-- verify -->
<!doctype html>
<html><head><meta charset="utf-8"><title>Cube</title>
<style>html,body{margin:0;overflow:hidden}canvas{display:block}</style></head>
<body>
<script type="importmap">{"imports":{"three":"./node_modules/three/build/three.module.js","three/addons/":"./node_modules/three/examples/jsm/"}}</script>
<script type="module">
import * as THREE from 'three';

const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(innerWidth, innerHeight);
document.body.appendChild(renderer.domElement);
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x1d2128);

// ask three for the context it already made, instead of asking the canvas for a WebGL 1 one
const gl = renderer.getContext();
const ext = gl.getExtension('WEBGL_debug_renderer_info');
window.gpuName = ext ? gl.getParameter(ext.UNMASKED_RENDERER_WEBGL) : gl.getParameter(gl.RENDERER);

const camera = new THREE.PerspectiveCamera(50, innerWidth / innerHeight, 0.1, 100);
camera.position.set(0, 1, 4);
camera.lookAt(0, 0, 0);
const cube = new THREE.Mesh(new THREE.BoxGeometry(), new THREE.MeshNormalMaterial());
cube.name = 'cube';
scene.add(cube);

addEventListener('resize', () => {
  renderer.setSize(innerWidth, innerHeight);
  camera.aspect = innerWidth / innerHeight;
  camera.updateProjectionMatrix();
});
renderer.setAnimationLoop(() => renderer.render(scene, camera));
</script>
</body></html>

After the change, open has no Errors so far block, observe reports "errors":[], and eval returns the GPU's name from three's own context:

open {"url":"fix.html","cwd":"<project>"}

Opened http://127.0.0.1:46045/fix.html in 0.6 s, paused at frame 0.
Page text outside the canvas: none
{"three":"186","scenes":1,"entities":["cube"]}

observe {}

{"entities":{"cube":{"type":"Mesh","position":[0,0,0]}},"harness":{"frame":1,"renderer":{"calls":1,"triangles":12,"textures":0,"geometries":1,"programs":2},"scene":{"objects":1,"meshes":1,"lights":0,"cameras":0},"errors":[],"changed":null}}

eval {"code":"return window.gpuName"}

ANGLE (Google, Vulkan 1.3.0 (SwiftShader Device (Subzero) (0x0000C0DE)), SwiftShader driver)

For a support check, use a fresh canvas, as WebGL.isWebGL2Available() in three/addons/capabilities/WebGL.js does, never the renderer's. For the GPU-side reasons, catch the constructor's throw and show the user a message that names the browser setting, since the page cannot fix the driver.

Where people ask about it

These threads quote the message; each count was read on 2026-10-28.