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
- Run the attachment flow in a real browser context (the chat UI), not SSR/Node tests.
- Check browser settings/extensions/policies that disable canvas (fingerprint blockers like CanvasBlocker).
- Free memory / reduce other canvas usage if allocation is failing, then retry.
- 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
- Only run image normalization in browser contexts, never SSR/Node tests
- Check for corporate policies or extensions blocking canvas fingerprinting
- Feature-detect canvas support before enabling image attachments in the UI
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
- Artifact store unavailable
- ${e instanceof Error ? e.message : String(e)}
- Image has no dimensions
- You can't use this function in a standalone script or flow s
- This function can only be called as a flow step
AI-assisted analysis of windmill-labs/windmill@e474e8803c (2026-09-03).
Data as JSON: /api/errors/758d468c39fa52b7.
Report an issue: GitHub.