Error Handling
Branch on error codes from any Polotno package — editor, exporters, importers, and polotno-node
Every Polotno package reports failures the same way: a plain Error with a code property and a structured, JSON-serializable details object. Consuming code branches on error.code — the same pattern Node.js itself uses (err.code === 'ENOENT'):
import { jsonToPDFBytes, type PolotnoError } from '@polotno/pdf-export';
try {
await jsonToPDFBytes(design);
} catch (e) {
const error = e as PolotnoError;
if (error.code === 'IMAGE_FAILED') {
// which element failed:
console.log(error.details.elementId);
if (error.details.reason === 'unsupported-format') {
// e.g. 'application/pdf' — an embedded PDF passed as an image
console.log(error.details.mimeType);
}
} else {
throw e;
}
}There are no error classes and no instanceof checks. A property check works across bundle copies, worker threads, and serialization boundaries — codes even survive the headless-browser boundary inside polotno-node, where thrown errors are rehydrated with code and details intact.
Codes vs. messages
Branch on error.code and details.reason, not on error.message. Codes are meant to stay stable across releases; messages are human-readable text and may be reworded at any time — log them, display them, but don't match on them.
Error codes
details.reason carries the fine grain within a code; the other details fields tell you what exactly failed.
| Code | When | Key details |
|---|---|---|
DESIGN_INVALID | Design JSON failed validation (schema check, store.loadJSON, unknown element type) | errors — array of { path, message } |
EXPORT_FAILED | An export could not run or an element failed to render | reason: page-not-found, workspace-not-mounted, unmounted-during-export, invalid-options, element, busy, pdfx — plus pageId, option, label, elementId |
IMAGE_FAILED | An image could not be loaded, decoded, or embedded | reason: unsupported-format, load, decode-failed, fetch-failed, invalid-data-url, timeout — plus mimeType, supported, src, elementId |
FONT_FAILED | A font could not be resolved or loaded | reason: not-found, timeout, fetch-failed, decode-failed — plus family, src |
VIDEO_FAILED | A video could not be loaded, decoded, or encoded | reason: load, no-video-track, unsupported-codec, cannot-decode, no-encoder, zero-duration, invalid-dimensions, timeout — plus src |
FETCH_FAILED | A network request for an asset failed (after retries, where applicable) | url, status, attempts |
IMPORT_FAILED | An imported file (PSD / SVG / PDF) could not be parsed | format: psd, svg, pdf |
UNKNOWN | An unclassified failure reported through onLoadError | — |
The original low-level error, when there is one, is chained as error.cause.
Where errors come from
Each package throws only the codes relevant to it:
| Package | Codes you can catch |
|---|---|
polotno (editor) | DESIGN_INVALID, EXPORT_FAILED from store methods; IMAGE_FAILED, VIDEO_FAILED, FONT_FAILED, UNKNOWN via onLoadError |
polotno-node | Everything the editor reports, rehydrated across the browser boundary, plus FETCH_FAILED and EXPORT_FAILED (reason: 'busy') |
@polotno/pdf-export | DESIGN_INVALID, IMAGE_FAILED, EXPORT_FAILED, FONT_FAILED, FETCH_FAILED |
@polotno/pptx-export | DESIGN_INVALID, IMAGE_FAILED, EXPORT_FAILED, FETCH_FAILED |
@polotno/video-export | VIDEO_FAILED, FETCH_FAILED, EXPORT_FAILED |
@polotno/psd-import, @polotno/svg-import, @polotno/pdf-import | IMPORT_FAILED |
@polotno/schema | DESIGN_INVALID |
onLoadError in the editor
Exports and imports throw. The editor canvas is different: a broken image or font inside the editor should not crash rendering, so those failures are reported instead — to the global onLoadError handler. The error object has exactly the same shape as thrown errors:
import { onLoadError } from 'polotno/config';
onLoadError((error) => {
if (error.code === 'IMAGE_FAILED') {
notifyUser(`An image could not be loaded (element ${error.details?.elementId})`);
} else if (error.code === 'FONT_FAILED') {
notifyUser(`Font "${error.details?.family}" could not be loaded`);
}
});See Asset Loading Configuration for timeouts and the rest of the loading configuration.
TypeScript
Every package exports the PolotnoError interface and a PolotnoErrorCode union of the codes that package throws, so comparing error.code against a misspelled code is a compile-time error:
import type { PolotnoError, PolotnoErrorCode } from '@polotno/pdf-export';
// in the editor:
import type { PolotnoError } from 'polotno/utils/errors';