Error handling
Typed errors thrown by exportToPDF and registerFont, with stable codes you can branch on.
react-print-pdf throws a custom PdfExportError from every public API failure. The error has a stable code you can switch on without parsing the message.
The PdfExportError shape
import { PdfExportError, isPdfExportError } from "react-print-pdf";
class PdfExportError extends Error {
readonly code: PdfExportErrorCode;
readonly context: PdfExportErrorContext;
// ES2022 standard `cause` field — set when an underlying error
// (fetch, FontFace.load) was wrapped.
readonly cause?: unknown;
}Use isPdfExportError(value) as a type guard inside catch blocks.
Codes
| Code | Where | Why it fires |
|---|---|---|
INVALID_ELEMENT | exportToPDF | The element argument was null or undefined. Usually a missing ref.current. |
FONT_FETCH_FAILED | registerFont | The font URL returned a non-2xx HTTP status. Check the URL is reachable and CORS-allowed. |
FONT_INVALID_BYTES | registerFont | The response body was shorter than 100 bytes. Almost always means the server sent an HTML 404 page or a redirect, not a font file. |
FONT_LOAD_FAILED | registerFont | The browser's FontFace.load() rejected the bytes. Common cause: the URL points at WOFF/WOFF2, which pdf-lib cannot embed. Use TTF or OTF. |
RENDER_DETACHED | internal (header / footer rerender) | The hidden React root was unmounted before rerender was called. Should not happen during a normal export. |
Codes are stable across patch releases. New codes may be added in minor releases. Renames are breaking changes.
Image-fetch and raster-fallback failures intentionally do not throw — they print a console.warn and the export continues with the failed asset replaced. Promoting them to errors would break documents that already tolerate lossy export. A future opt-in onError callback in ExportOptions will let you escalate these to thrown errors per-call.
Branching example
import { exportToPDF, isPdfExportError } from "react-print-pdf";
async function handleExport(el: HTMLElement) {
try {
const { blob } = await exportToPDF(el, { filename: "report.pdf" });
download(blob);
} catch (e) {
if (isPdfExportError(e)) {
switch (e.code) {
case "INVALID_ELEMENT":
toast.error("The export target hasn't mounted yet — try again in a moment.");
break;
case "FONT_FETCH_FAILED":
case "FONT_INVALID_BYTES":
case "FONT_LOAD_FAILED":
// Log structured context for the bug tracker.
reportToSentry({ code: e.code, ...e.context, cause: e.cause });
toast.error("Failed to load a custom font — falling back to defaults.");
break;
default:
toast.error("Export failed. The team has been notified.");
reportToSentry({ code: e.code, message: e.message });
}
return;
}
// Non-PdfExportError (probably a pdf-lib bug or unhandled DOM error).
reportToSentry(e);
toast.error("Unexpected error.");
}
}Diagnostic context
error.context carries free-form fields useful for logs and bug reports. They are not part of the public branching contract — fields may appear or disappear between minor releases.
| Field | Type | Set by |
|---|---|---|
url | string | FONT_* |
status | number | FONT_FETCH_FAILED |
byteLength | number | FONT_INVALID_BYTES, FONT_LOAD_FAILED |
detail | string | FONT_LOAD_FAILED (underlying browser error message) |
Always log error.code, error.message, error.context, and error.cause together. That tuple is enough to reproduce 95% of the failure modes we've seen in the wild.