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
- Treat INVALID as 'no scene present' and fall back to importing the PNG as a plain image element rather than as a scene.
- If a scene is expected, re-export the PNG from Excalidraw with 'Embed scene' / exportEmbedScene enabled so the tEXt chunk is written.
- Verify the PNG was not passed through an optimizer (tinypng, imagemagick with strip) that drops ancillary chunks.
- 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
- Use getTEXtChunk to branch before calling decodePngMetadata.
- Expect INVALID for any non-Excalidraw PNG; do not treat it as a bug.
- Disable PNG optimizers that strip tEXt chunks when embedding scenes.
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.