Web SDKv1.0.0

Errors

Every failure the SDK reports is an SDKError with a stable code you can switch on. Nothing is thrown as a bare string.

SDKError

FieldTypeDescription
codeErrorCodeValueMachine-readable error code, one of the ErrorCode values. Switch on this; never match on message.
messagestringHuman-readable description, for logs rather than end users. Not stable API.
fatalbooleanWhether the SDK itself is unusable after this error. false by default; new SDKError(code, message, fatal = false) is public, for test doubles.
cause?unknownWhat failed underneath, where there is something: activateTool() rejects with UNKNOWN_ERROR carrying the tool's own failure as its cause.

fatal: true means the SDK itself is unusable: the engine failed to load (ENGINE_LOAD_FAILED) or the SDK stopped on an unrecoverable fault (ENGINE_FAILED), or init() was never called. Re-initialize, or stop. Everything else is fatal: false — the one operation failed and the SDK and the viewer are as they were: a page that did not render, a file that would not open (CORS_ERROR, NETWORK_ERROR, FORMAT_ERROR, PASSWORD_ERROR, SECURITY_ERROR — offer another file), an argument a call could not act on. fatal never says "the document is unusable"; the code does.

Handling

TypeScript
import { Kernstra, SDKError, ErrorCode } from '@kernstra/pdf-web-sdk';

try {
  await Kernstra.init({ license: import.meta.env.KERNSTRA_LICENSE });
} catch (err) {
  if (err instanceof SDKError) {
    // err.code is machine-readable; err.message is for logs, not users.
    if (err.code === ErrorCode.LICENSE_REQUIRED) {
      showLicenseHelp(Kernstra.getLicense());
    } else if (err.code === ErrorCode.ENGINE_LOAD_FAILED) {
      showSetupHelp();
    }
    if (err.fatal) {
      teardownAndReinitialize();
    }
  }
}

// Reported errors have no call to catch: they reach the viewer's error event.
viewer.on('error', ({ code, message, fatal }) => {
  if (fatal) {
    showReloadPrompt();
  } else if (code === ErrorCode.RENDER_FAILED) {
    // A page, a print job or the render worker; nothing to do but note it.
    telemetry.warn('pdf.render', message);
  }
});

Codes

CodeWhenWhat to do
CORS_ERRORLoading by URL: the request was blocked before a response — usually the host does not allow cross-origin requests, but Chrome and Firefox report an offline or unreachable host the same way. With credentials: 'include' a cross-origin server must answer the page's exact origin and Access-Control-Allow-Credentials: true.Allow your origin on the file host (or proxy the file through your own), check that the host is reachable, and load again. Not fatal: offer another file.
NETWORK_ERRORLoading by URL: the server answered with an error status (e.g. 404; a 401 or 403 has its message end in the status) or the request failed in another way. doc.whenFullyLoaded(): a progressive download could not finish — a range request kept failing through its three retries, or the server reported a size the SDK cannot load (4 GiB or more) or a first range that was not the one asked for; the viewer reports it too. uploadTo(): the request could not be sent at all.Check the URL and connectivity; for 401 / 403 refresh the token and call again. A progressive document stays open for viewing. Not fatal.
FORMAT_ERRORLoading, doc.pages.import(), Import PDF in Manage Pages (reported): not a PDF, the file is corrupt, or it claims pages it does not hold (a page tree whose count the file cannot back).Offer another file — createLoadError() shows it in place. Not fatal.
PASSWORD_ERRORLoading, doc.pages.import(), Import PDF in Manage Pages (reported): a password is required, or the one supplied is wrong. Manage Pages asks for none.Prompt for a password (createPasswordPrompt()) and load again with { password }; import an encrypted file with doc.pages.import(bytes, { position, password }).
SECURITY_ERRORLoading, doc.pages.import(), Import PDF in Manage Pages (reported): the file is encrypted with a security handler the SDK cannot open.The document cannot be opened as supplied; offer another file. Not fatal.
PAGE_ERRORdoc.pages, doc.annotations, doc.text, bookmarks, attachments, doc.security, viewer.tools.editText, Manage Pages (reported), and viewer.whenReady() when the file's fields or pages cannot be wired: the SDK could not carry out a sound request on this document — a page that could not be loaded, copied, moved or created, a page tree nested too deeply for its pages to be changed (insert, delete, move, duplicate, import — rotate and extract still work), a page with form fields that could not be flattened for import, an annotation, bookmark, attachment or encryption that could not be written.A state of the document, not of your code: report it and leave the document as it is. The viewer stands; the error event reports it too. Not fatal.
RENDER_FAILEDRendering, printing, the render worker. Reported, not thrown — the page or print job failed, or the render worker did (rendering continues on the main thread).Nothing to do but note it: the viewer stays usable. Not fatal.
ENGINE_LOAD_FAILEDKernstra.init(): the asset store could not be reached on a first load, or served bytes that are not this SDK version's engine; or a self-hosted engineUrl is wrong.Fatal. Fix the store's reachability (CSP connect-src, the network) or the self-hosted engineUrl, then call Kernstra.init() again — a failed call starts over on the next.
ENGINE_FAILEDAny document or viewer call, after the SDK stopped on an unrecoverable fault (a damaged or hostile file can cause one). Every later call answers this code. In the render worker it is reported, not fatal: the worker stops and rendering continues on the main thread.Fatal. Nothing renders or saves until Kernstra.destroy() and init() again, then the document opened afresh. destroy() tears down whatever is open after this fault — that is the recovery.
NOT_INITIALIZEDLoading, new PDFViewer: a document was loaded, or a viewer constructed, before Kernstra.init() resolved.Await Kernstra.init() at application startup before loading documents or constructing viewers. fatal is true when init() was never called.
OUT_OF_MEMORYAny document call: the SDK could not allocate the memory one operation needed, usually for a very large page or document.Not fatal — the next call works. Retry, or work on a smaller page or document.
DOCUMENT_CLOSEDAny document call: the document was closed or destroyed. The getters (pageCount, metadata, pages.count, …) throw it synchronously; every promise-returning member rejects with it. A viewer still open on a closed document refuses undo, redo, snapshot() and Download with a reported DOCUMENT_CLOSED, and its exporter rejects with it.Check doc.isClosed; destroy the viewer or open a new document. A document closed after an ENGINE_FAILED fault is already released and its close() does nothing.
EXPORT_FAILEDExport, doc.save(), doc.pages.extract(), Extract in Manage Pages (reported): the document could not be written — including a changed PDF/A file whose claim cannot be removed. Thrown by the exporter's methods and doc.save(); reported (not thrown) when the toolbar's Download fails, and when an undo snapshot could not be recorded.Read message, then retry. A change whose undo step could not be recorded still counts: hasUnsavedChanges turns true and documentChanged reports it.
LICENSE_REQUIREDKernstra.init(), PDFDocument.fromUrl() / fromFile() / fromBytes() / fromUrlProgressive(), doc.pages.import(), Import PDF in Manage Pages (reported): no license, or one that is expired, revoked, paused, for another origin, or unverifiable on a plain-HTTP page (reason insecure-context). The loaders refuse a stopped license only (not a signed-out one): no document opens under it.Pass a valid token to Kernstra.init({ license }) or Kernstra.setLicense(token) on a secure context (HTTPS or localhost); Kernstra.getLicense() says why the current one does not load. A paused license loads again once the licensing API says it is back in force. See Licensing.
FEATURE_NOT_LICENSEDTools (activateTool, and the toolbar never shows them), export (doc.save(), doc.pages.extract() and Extract in Manage Pages, reported, too, and doc.security.protect() / removeProtection()), changing the document's structure (edit-pages: doc.pages.insert() / delete() / move() / rotate() / setRotation() / duplicate() / import(), doc.outline.add() / delete() / set() / setTitle(), doc.attachments.add() / delete()), print, forms, annotations (forms too for a form field's widget), reading content out (extract: doc.text, doc.forms.fields, doc.forms.getValues, doc.attachments.data, viewer.search, and opening or retyping a block through viewer.tools.editText); init for engineUrl / shaperUrl / subsetterUrl; the first text tool, or new FontRegistry(), for fonts.fontsBaseUrl. The license is valid but its features do not include what was asked, or a self-hosting URL was set without self-hosting in the license.Check Kernstra.getLicense(): state.valid && state.features.includes('export'). Mirror viewer.isToolEnabled(tool) in your own chrome rather than offering a tool the license does not cover. See Licensing.
INVALID_ARGUMENTProgrammatic API: a call was given an argument it cannot act on — a page index out of range, an annotation id no annotation has, a bookmark path or attachment index that names nothing, an empty attachment name, deleting the only page, extracting or exporting no pages or a page the document lacks, a rotation that is not a multiple of 90, a position that is not a whole number, a text block id that names no block, recoloring a text box, an empty search query, goToPage() / setScale() out of range, print({ pages }) with an empty list or a page the document has not, activateTool() for a name that is no tool or a tool this viewer's configuration turned off, uploadTo() headers holding a name or value a request cannot carry, a second Kernstra.init() with other options, a styleNonce that is not base64 or base64url, or a licensePopup part that is not a string.A bug in the calling code, not a state of the document: fix the argument.
INVALID_STATEA call that needs something first, or that a signed document rules out: a tool handle used while its tool is not active; a text block that kept losing its editing surface to another; on a progressive document still downloading, doc.save(), doc.pages.extract() and every write through doc.annotations, doc.forms, doc.pages, doc.outline, doc.attachments and doc.security; protecting, unprotecting or flattening a signed document, or a write a certified document's certification forbids, without invalidateSignatures: true; every acting member of a destroyed viewer; Kernstra.destroy() with a document open; an export from createExporter() whose pending Edit Text edits cannot be written, or whose document was reloaded under it.Do the prerequisite first: await viewer.activateTool('editText') before using its handle, await doc.whenFullyLoaded() before writing (an export from createExporter() waits by itself), pass invalidateSignatures: true to go ahead on a signed document, destroy viewers and close documents before Kernstra.destroy(), export again after a reload.
PERMISSION_DENIEDactivateTool, print: the document's permissions forbid it and it was not opened with its owner password.There is no option to lift these: only the owner password does (doc.security.unlockOwnerPermissions(password)). viewer.toolBlockedReason(tool) gives the reason for a host's own chrome.
ABORTEDPDFDocument.fromUrl() / fromUrlProgressive(), doc.whenFullyLoaded(), export and uploadTo(), viewer.whenReady() when destroy() comes first: the AbortSignal you passed was aborted before the work was done.Expected when you canceled; nothing to repair.
UNKNOWN_ERRORAnywhere: unclassified. From activateTool(), a tool that could not start — nothing of it stays, the viewer goes back to the resting tool, and the cause is its cause.Read message (and cause where set); log it, then retry or report.