danny-avila/LibreChat · error · Error

Canvas is unavailable for Mermaid PNG export

Error message

Canvas is unavailable for Mermaid PNG export

What it means

Thrown by the PNG export path when `document.createElement('canvas').getContext('2d')` returns null. A null 2D context means the browser either does not expose the Canvas 2D API or has exhausted its per-page context limit (browsers cap simultaneous WebGL/2D contexts; exceeding it returns null for new ones). This is rare in modern browsers but real in constrained/headless environments.

Source

Thrown at client/src/utils/diagram/export.ts:229

  triggerDownload(url, exportFilename(filename, 'svg'));
}

export async function downloadMermaidPng(
  svg: string,
  filename: string,
  dimensions?: MermaidDimensions | null,
  background?: string,
): Promise<void> {
  const sourceUrl = await blobDataUrl(svgBlob(svg));
  const image = await loadSvgImage(sourceUrl);
  const sourceWidth = dimensions?.width || image.naturalWidth || image.width;
  const sourceHeight = dimensions?.height || image.naturalHeight || image.height;
  const canvasDimensions = resolveCanvasDimensions(sourceWidth, sourceHeight);
  const canvas = document.createElement('canvas');
  const context = canvas.getContext('2d');

  if (!context) {
    throw new Error('Canvas is unavailable for Mermaid PNG export');
  }

  canvas.width = canvasDimensions.width;
  canvas.height = canvasDimensions.height;
  context.imageSmoothingEnabled = true;
  context.imageSmoothingQuality = 'high';
  if (background) {
    context.fillStyle = background;
    context.fillRect(0, 0, canvas.width, canvas.height);
  }
  context.drawImage(image, 0, 0, canvas.width, canvas.height);

  const png = await encodePng(canvas);
  const outputUrl = URL.createObjectURL(png);
  triggerDownload(outputUrl, exportFilename(filename, 'png'));
}

View on GitHub (pinned to 5ff282f900)

Solutions

  1. In tests, install/configure the `canvas` package for jsdom, or mock `downloadMermaidPng`.
  2. In production, free unused canvas contexts (set width/height to 0 and let them be GC'd) to avoid hitting the per-page context cap.
  3. Detect the null context and fall back to SVG export (or a server-side rasterization) instead of failing.
  4. If a fingerprinting shield is the cause, document that PNG export requires canvas and offer SVG download as an alternative.

Example fix

// before
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
if (!context) {
  throw new Error('Canvas is unavailable for Mermaid PNG export');
}
// after — fall back to SVG download when a 2D context cannot be obtained
const context = canvas.getContext('2d');
if (!context) {
  logger.warn('Canvas 2D unavailable; falling back to SVG export');
  return downloadMermaidSvg(svg, filename);
}
Defensive patterns

Strategy: fallback

Validate before calling

// Probe canvas 2D support before offering PNG export
function canvas2dAvailable(): boolean {
  try {
    return !!document.createElement('canvas').getContext('2d');
  } catch { return false; }
}

Type guard

function hasCanvas2D(): boolean {
  if (typeof document === 'undefined') return false;
  const c = document.createElement('canvas');
  return c.getContext('2d') != null;
}

Try / catch

const context = canvas.getContext('2d');
if (!context) {
  // Fall back to SVG download instead of failing the export entirely
  return downloadMermaidSvg(svg, filename);
}

Prevention

When it happens

Trigger: Running the export in a headless test runner (jsdom) that does not implement `canvas.getContext('2d')` without the `canvas` npm package; a page that leaked many canvas contexts and hit the browser limit; an embedded WebView/older browser without 2D canvas; a hardening extension that blocks canvas (fingerprinting protection returning null).

Common situations: Unit/integration tests under jsdom without canvas polyfill; privacy browsers/extensions (Brave, Tor, fingerprinting shields) that neuter canvas; long-lived SPA that created hundreds of canvases; embedded WebView in a native app with limited canvas support.

Related errors


AI-assisted analysis of danny-avila/LibreChat@5ff282f900 (2026-08-12). Data as JSON: /api/errors/de16bb3411d518c2. Report an issue: GitHub.