windmill-labs/windmill · error

Canvas 2D context unavailable

Error message

Canvas 2D context unavailable

What it means

After sizing an offscreen canvas, normalizeImageDataUrl() calls canvas.getContext('2d'); the Canvas API is allowed to return null (e.g. when the context type is unsupported or canvas creation is blocked by browser policy/hardware limits). Since drawing is impossible without the context, the adapter throws this error rather than crashing later inside drawImage with a confusing null dereference.

Source

Thrown at frontend/src/lib/components/copilot/chat/imageUtils.ts:161

 */
export async function normalizeImageDataUrl(
	dataUrl: string,
	name?: string,
	maxEdge: number = MAX_IMAGE_EDGE
): Promise<AttachedImage> {
	const img = await loadImage(dataUrl)
	const srcW = img.naturalWidth || img.width
	const srcH = img.naturalHeight || img.height
	if (!srcW || !srcH) throw new Error('Image has no dimensions')
	if (srcW * srcH > MAX_IMAGE_PIXELS) throw new Error('Image resolution is too large')
	const scale = Math.min(1, maxEdge / Math.max(srcW, srcH))
	const w = Math.max(1, Math.round(srcW * scale))
	const h = Math.max(1, Math.round(srcH * scale))
	const canvas = document.createElement('canvas')
	canvas.width = w
	canvas.height = h
	const ctx = canvas.getContext('2d')
	if (!ctx) throw new Error('Canvas 2D context unavailable')
	ctx.drawImage(img, 0, 0, w, h)
	return { ...encodeCanvas(canvas), name }
}

/** Read a user-provided image file and produce a bounded, model-ready AttachedImage. */
export async function fileToAttachedImage(file: File | Blob): Promise<AttachedImage> {
	if (file.size > MAX_IMAGE_BYTES) throw new Error('Image file is too large')
	const name = file instanceof File ? file.name : undefined
	const dataUrl = await blobToDataUrl(file)
	return await normalizeImageDataUrl(dataUrl, name)
}

/** Split a data URL into its media type and base64 payload (for the Anthropic converter). */
export function parseImageDataUrl(url: string): { mediaType: string; base64: string } {
	const match = /^data:([^;,]+)?(;base64)?,(.*)$/s.exec(url)
	if (!match) return { mediaType: 'image/png', base64: '' }
	return { mediaType: match[1] || 'image/png', base64: match[2] ? match[3] : '' }
}

View on GitHub (pinned to e474e8803c)

Solutions

  1. Run the attachment flow in a real browser context (the chat UI), not SSR/Node tests.
  2. Check browser settings/extensions/policies that disable canvas (fingerprint blockers like CanvasBlocker).
  3. Free memory / reduce other canvas usage if allocation is failing, then retry.
  4. Catch the error and offer a fallback that skips normalization (send the original data URL if within limits) or show a friendly message.

Example fix

// before
const img = await fileToAttachedImage(file) // in vitest/jsdom
// after
if (typeof document === 'undefined' || !document.createElement('canvas').getContext('2d')) {
  throw new Error('Image normalization requires a browser canvas')
}
const img = await fileToAttachedImage(file)
Defensive patterns

Strategy: fallback

Validate before calling

function canvas2dAvailable(): boolean {
  try { return !!document.createElement('canvas').getContext('2d') } catch { return false }
}
if (!canvas2dAvailable()) throw new Error('Canvas unavailable in this environment')

Type guard

function has2dContext(c: HTMLCanvasElement): c is HTMLCanvasElement & { getContext(t: '2d'): CanvasRenderingContext2D } {
  return c.getContext('2d') !== null
}

Try / catch

try {
  const attached = await fileToAttachedImage(file)
} catch (e) {
  if (e instanceof Error && e.message === 'Canvas 2D context unavailable') {
    notify('Your browser/environment cannot process images (canvas disabled)')
  } else throw e
}

Prevention

When it happens

Trigger: canvas.getContext('2d') returns null — browsers with canvas disabled via policy (e.g. some hardened enterprise setups), devices where GPU-backed canvas allocation fails, or non-browser environments (SSR/tests without a DOM canvas implementation).

Common situations: Running chat attachment code in an SSR or Node test environment where 'canvas' isn't implemented; corporate browser policies disabling 2D canvas (fingerprinting protection); memory exhaustion preventing canvas backing-store allocation.

Related errors


AI-assisted analysis of windmill-labs/windmill@e474e8803c (2026-09-03). Data as JSON: /api/errors/758d468c39fa52b7. Report an issue: GitHub.