Polotno
Misc

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.

CodeWhenKey details
DESIGN_INVALIDDesign JSON failed validation (schema check, store.loadJSON, unknown element type)errors — array of { path, message }
EXPORT_FAILEDAn export could not run or an element failed to renderreason: page-not-found, workspace-not-mounted, unmounted-during-export, invalid-options, element, busy, pdfx — plus pageId, option, label, elementId
IMAGE_FAILEDAn image could not be loaded, decoded, or embeddedreason: unsupported-format, load, decode-failed, fetch-failed, invalid-data-url, timeout — plus mimeType, supported, src, elementId
FONT_FAILEDA font could not be resolved or loadedreason: not-found, timeout, fetch-failed, decode-failed — plus family, src
VIDEO_FAILEDA video could not be loaded, decoded, or encodedreason: load, no-video-track, unsupported-codec, cannot-decode, no-encoder, zero-duration, invalid-dimensions, timeout — plus src
FETCH_FAILEDA network request for an asset failed (after retries, where applicable)url, status, attempts
IMPORT_FAILEDAn imported file (PSD / SVG / PDF) could not be parsedformat: psd, svg, pdf
UNKNOWNAn 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:

PackageCodes you can catch
polotno (editor)DESIGN_INVALID, EXPORT_FAILED from store methods; IMAGE_FAILED, VIDEO_FAILED, FONT_FAILED, UNKNOWN via onLoadError
polotno-nodeEverything the editor reports, rehydrated across the browser boundary, plus FETCH_FAILED and EXPORT_FAILED (reason: 'busy')
@polotno/pdf-exportDESIGN_INVALID, IMAGE_FAILED, EXPORT_FAILED, FONT_FAILED, FETCH_FAILED
@polotno/pptx-exportDESIGN_INVALID, IMAGE_FAILED, EXPORT_FAILED, FETCH_FAILED
@polotno/video-exportVIDEO_FAILED, FETCH_FAILED, EXPORT_FAILED
@polotno/psd-import, @polotno/svg-import, @polotno/pdf-importIMPORT_FAILED
@polotno/schemaDESIGN_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';

On this page