excalidraw/excalidraw · warning · Error

INVALID

Error message

INVALID

What it means

Thrown by decodePngMetadata when the PNG has no usable Excalidraw tEXt chunk — either getTEXtChunk returned null (no tEXt chunk at all) or the tEXt chunk keyword is not MIME_TYPES.excalidraw. Unlike FAILED, INVALID is an expected outcome for ordinary PNGs and means 'no embedded scene here'.

Source

Thrown at packages/excalidraw/data/image.ts:70

    try {
      const encodedData = JSON.parse(metadata.text);
      if (!("encoded" in encodedData)) {
        // legacy, un-encoded scene JSON
        if (
          "type" in encodedData &&
          encodedData.type === EXPORT_DATA_TYPES.excalidraw
        ) {
          return metadata.text;
        }
        throw new Error("FAILED");
      }
      return decode(encodedData);
    } catch (error: any) {
      console.error(error);
      throw new Error("FAILED");
    }
  }
  throw new Error("INVALID");
};

View on GitHub (pinned to abeeaeba21)

Solutions

  1. Treat INVALID as 'no scene present' and fall back to importing the PNG as a plain image element rather than as a scene.
  2. If a scene is expected, re-export the PNG from Excalidraw with 'Embed scene' / exportEmbedScene enabled so the tEXt chunk is written.
  3. Verify the PNG was not passed through an optimizer (tinypng, imagemagick with strip) that drops ancillary chunks.
  4. Pre-check with getTEXtChunk(blob) before calling decodePngMetadata to branch cleanly.

Example fix

// before
const scene = await decodePngMetadata(pngBlob); // throws INVALID on plain PNG

// after: probe first, fall back to image import
import { getTEXtChunk } from "@excalidraw/excalidraw/data/image";
const chunk = await getTEXtChunk(pngBlob);
if (chunk?.keyword === MIME_TYPES.excalidraw) {
  const scene = await decodePngMetadata(pngBlob);
} else {
  // insert as image element instead
}
Defensive patterns

Strategy: validation

Validate before calling

import { getTEXtChunk } from "@excalidraw/excalidraw/data/image";
import { MIME_TYPES } from "@excalidraw/common";

const chunk = await getTEXtChunk(blob);
const hasExcalidrawScene = chunk?.keyword === MIME_TYPES.excalidraw;
if (!hasExcalidrawScene) {
  // skip decodePngMetadata; import as image instead
}

Type guard

const hasExcalidrawTEXtChunk = async (blob: Blob): Promise<boolean> => {
  const chunk = await getTEXtChunk(blob);
  return chunk?.keyword === MIME_TYPES.excalidraw;
};

Try / catch

try { await decodePngMetadata(blob); }
catch (e) { if (e.message === "INVALID") { /* no scene — handle gracefully */ } }

Prevention

When it happens

Trigger: Passing a plain PNG with no tEXt chunk; passing a PNG whose tEXt chunk is keyed for a different application (e.g. 'Comment', 'Software'); or calling decodePngMetadata on a PNG exported with 'embed scene' disabled.

Common situations: Treating an arbitrary uploaded PNG as an Excalidraw scene, importing a PNG that was re-saved by an image editor that strips or renames metadata chunks, or a user opening a non-Excalidraw PNG via the scene-import path.

Related errors


AI-assisted analysis of excalidraw/excalidraw@abeeaeba21 (2026-08-12). Data as JSON: /api/errors/078ad5b0e3bc182e. Report an issue: GitHub.