Web SDKv1.0.0

PDFDocument

An open PDF: loaded through one of four static factories, read through its properties, changed through its namespaces (pages, annotations, forms, text, security, outline, attachments, signatures) and serialized with save(). Kernstra.init() must complete before any factory is called: all four require the engine to be loaded, and each is a load for the license, refused with LICENSE_REQUIRED under a stopped license.

Every method that reads or changes the document returns a promise — await it, and catch its SDKError as a rejection; the getters (pageCount, metadata, pages.count, …) answer at once, and are the only members that throw (DOCUMENT_CLOSED, on a closed document). The work runs on the calling thread, so a change is complete, and its events fired, before the call returns; await the promise all the same.

Keeping a viewer in step. Every mutating call announces itself. A viewer attached to the same document reloads, re-renders, and pushes an undo entry, so await doc.pages.insert({ position: { after: 0 } }) followed by await viewer.undo() removes the blank page again. Nothing extra is needed to wire that up — attaching the document is enough.

classawait PDFDocument.fromUrl(url, options?) | fromFile(file, options?) | fromBytes(bytes, options?) | fromUrlProgressive(url, options?)

Usage

TypeScript
await Kernstra.init({ license: import.meta.env.KERNSTRA_LICENSE });

const doc = await PDFDocument.fromUrl('/contract.pdf');
console.log(doc.pageCount, doc.metadata.title);

await doc.pages.insert({ position: { after: 0 } });
const bytes = await doc.save();
await doc.close();

Loading

fromUrlstatic methodsince 1.0.0
static fromUrl(url: string, options?: OpenUrlOptions): Promise<PDFDocument>
Load a PDF from a URL (handles CORS errors). A same-origin URL sends the cookie by default (credentials: 'same-origin'). A cross-origin one needs credentials: 'include', and the server must then answer the page's exact origin and Access-Control-Allow-Credentials: true — a wildcard origin fails as CORS_ERROR. A 401 or 403 rejects with NETWORK_ERROR; its message ends in the status (HTTP 401). A token that must be refreshed per request is the host's to fetch: download the bytes with your own request and pass them to fromBytes. See Document features.
ParameterTypeDescription
urlstringThe URL to fetch.
options?OpenUrlOptions{ password?, signal?, headers?, credentials? }. password opens an encrypted file; signal, headers and credentials go to fetch() as given, which reaches a protected URL (an Authorization header, or credentials: 'include' for the session cookie on a cross-origin host). The headers are read once, at the call.
Returns Promise<PDFDocument>
  • ABORTED — the signal was aborted.
  • CORS_ERROR — the server did not allow the page's origin.
  • NETWORK_ERROR — the request failed, including 401 and 403.
  • NOT_INITIALIZED — Kernstra.init() has not resolved.
  • FORMAT_ERROR — not a PDF, or a corrupt one.
  • PASSWORD_ERROR — a password is required, or the one supplied is wrong.
  • SECURITY_ERROR — encrypted with a security handler the SDK cannot open.
  • LICENSE_REQUIRED — opened under a stopped license (paused, revoked, expired, a copied or forged key): no document opens.
  • OUT_OF_MEMORY — the SDK could not allocate the memory the file needs.
  • ENGINE_FAILED — the SDK stopped on an unrecoverable fault (fatal).
TypeScript
const doc = await PDFDocument.fromUrl('/api/documents/123', {
  headers: { Authorization: 'Bearer ' + token },
});
fromFilestatic methodsince 1.0.0
static fromFile(file: File, options?: OpenOptions): Promise<PDFDocument>
Load a PDF from a File object (file picker).
ParameterTypeDescription
fileFileA File, from a file picker or a drop.
options?OpenOptions{ password? } — the password of an encrypted file.
Returns Promise<PDFDocument>
  • NOT_INITIALIZED — Kernstra.init() has not resolved.
  • FORMAT_ERROR — not a PDF, or a corrupt one.
  • PASSWORD_ERROR — a password is required, or the one supplied is wrong.
  • SECURITY_ERROR — encrypted with a security handler the SDK cannot open.
  • LICENSE_REQUIRED — opened under a stopped license: no document opens.
  • OUT_OF_MEMORY — the SDK could not allocate the memory the file needs.
  • ENGINE_FAILED — the SDK stopped on an unrecoverable fault (fatal).
TypeScript
input.addEventListener('change', async () => {
  const [file] = input.files ?? [];
  if (file) doc = await PDFDocument.fromFile(file);
});
fromBytesstatic methodsince 1.0.0
static fromBytes(bytes: Uint8Array, options?: OpenOptions): Promise<PDFDocument>
Load a PDF from a Uint8Array.
ParameterTypeDescription
bytesUint8ArrayThe PDF bytes.
options?OpenOptions{ password? } — the password of an encrypted file.
Returns Promise<PDFDocument>
  • NOT_INITIALIZED — Kernstra.init() has not resolved.
  • FORMAT_ERROR — not a PDF, or a corrupt one.
  • PASSWORD_ERROR — a password is required, or the one supplied is wrong.
  • SECURITY_ERROR — encrypted with a security handler the SDK cannot open.
  • LICENSE_REQUIRED — opened under a stopped license: no document opens.
  • OUT_OF_MEMORY — the SDK could not allocate the memory the file needs.
  • ENGINE_FAILED — the SDK stopped on an unrecoverable fault (fatal).
TypeScript
const bytes = new Uint8Array(await (await fetch('/terms.pdf')).arrayBuffer());
const doc = await PDFDocument.fromBytes(bytes);
fromUrlProgressivestatic methodsince 1.0.0
static fromUrlProgressive(url: string, options?: OpenProgressiveOptions): Promise<PDFDocument>

Open over HTTP range requests, before the whole file has arrived: the viewer appears and the first pages become viewable after a small fraction of the file has downloaded, while the rest streams in the background. Works best with linearized ("fast web view") PDFs but benefits any PDF. Requires a server that honors Range requests (checked automatically; falls back to a normal full download when it does not).

Until isFullyLoaded is true the document is view-only: the editing tools are grayed out, and undo snapshots, page management, search and export wait for completion (whenFullyLoaded()). Form fields can be filled meanwhile. The document API does not wait: save(), pages.extract() and every write through its namespaces reject with INVALID_STATE until then. Pages scrolled to before their bytes arrive download on demand (ensurePageAvailable() — the viewer does this automatically for pages and thumbnails).

ParameterTypeDescription
urlstringThe URL to open over HTTP range requests.
options?OpenProgressiveOptionsWhat fromUrl takes (every range request carries headers, credentials and signal) plus chunkSize (range-request size in bytes, default 256 KiB) and onProgress ((downloaded, total) => void).
Returns Promise<PDFDocument>
  • ABORTED — the signal was aborted.
  • CORS_ERROR — the server did not allow the page's origin.
  • NETWORK_ERROR — the request failed, or the progressive download could not finish.
  • NOT_INITIALIZED — Kernstra.init() has not resolved.
  • FORMAT_ERROR — not a PDF, or a corrupt one.
  • PASSWORD_ERROR — a password is required, or the one supplied is wrong.
  • SECURITY_ERROR — encrypted with a security handler the SDK cannot open.
  • LICENSE_REQUIRED — opened under a stopped license: no document opens.
TypeScript
const doc = await PDFDocument.fromUrlProgressive('/big-report.pdf', {
  chunkSize: 256 * 1024, // range-request size (default 256 KiB)
  onProgress: (loaded, total) => updateProgressBar(loaded / total),
});

Properties

pageCountgettersince 1.0.0
get pageCount(): number
Total number of pages.
Returns number
  • DOCUMENT_CLOSED — read on a closed document.
TypeScript
const last = doc.pageCount - 1;
metadatagettersince 1.0.0
get metadata(): DocumentMetadata

Title, author, subject, creator, keywords, producer, creationDate, modifiedDate. modifiedDate is the file's modification date, which a save sets — the saved copy's; the open document's stays what the file said.

Without the extract feature the six strings are ''.

Returns DocumentMetadata title, author, subject, keywords (as the file lists them, one string), creator (the application that produced the document), producer (the application that wrote the file), creationDate and modifiedDate (Date | null, null when the file does not say).
  • DOCUMENT_CLOSED — read on a closed document.
TypeScript
document.title = doc.metadata.title || file.name;
pdfAgettersince 1.0.0
get pdfA(): PDFAClaim | null
The PDF/A conformance the file claims in its XMP metadata ({ part: 2, conformance: 'B' }), or null. A claim, not a check: the SDK does not validate it, and does not check what it writes against PDF/A, so a saved file keeps the claim only when nothing changed it. A changed file is saved without the claim, a plain PDF; when the claim cannot be edited out of the metadata, the metadata packet is removed, and when that cannot be done either save() fails with EXPORT_FAILED rather than hand out a file that claims PDF/A. A claim stored in compressed metadata, or in a packet over 1 MiB, is not read.
Returns PDFAClaim | null { part, conformance? }, or null.
  • DOCUMENT_CLOSED — read on a closed document.
TypeScript
if (doc.pdfA) console.log('PDF/A-' + doc.pdfA.part + (doc.pdfA.conformance ?? ''));
hasPasswordgettersince 1.0.0
get hasPassword(): boolean
Whether the document was opened with a password.
Returns boolean
isClosedgettersince 1.0.0
get isClosed(): boolean
Whether close() has been called.
Returns boolean
containsJavaScriptgettersince 1.0.0
get containsJavaScript(): boolean
The document declares embedded JavaScript. The SDK never executes it; use this to screen untrusted uploads or warn the user.
Returns boolean
  • DOCUMENT_CLOSED — read on a closed document.
TypeScript
if (doc.containsJavaScript) showWarning('This file contains scripts');
isProgressivegettersince 1.0.0
get isProgressive(): boolean
Opened via fromUrlProgressive().
Returns boolean
isFullyLoadedgettersince 1.0.0
get isFullyLoaded(): boolean
All bytes have arrived. Always true for non-progressive documents.
Returns boolean
loadedByteLengthgettersince 1.0.0
get loadedByteLength(): number
The file's size in bytes, as opened.
Returns number

Lifecycle

savemethodsince 1.0.0
save(): Promise<Uint8Array>

Resolves to the document's bytes, a Uint8Array: every change made through the API and, with a viewer open, the values its form fields hold — not a signature drawn on screen (use viewer.createExporter() for that, when a viewer is on screen and you want everything the user sees). Values set with doc.forms while no viewer was open are in the document, and so in the save. Needs the export feature.

A document opened with a digital signature is saved as an incremental update: the bytes it was signed over, untouched, then one update with what changed — nothing the session passed through on the way is in the file, and a document saved unchanged comes back as the signed file itself. A change the signature's certification allows then reads in Acrobat as a change made after signing, not as a broken signature. Any other document is written afresh. Saving stamps the saved copy's modification date; the open document is left as it was, and a PDF/A file nobody changed is saved as it was opened. Encryption applied with security.protect() reaches the file through these bytes — nothing on disk changes until you write them somewhere.

Returns Promise<Uint8Array> The document's bytes.
  • FEATURE_NOT_LICENSED — the license lacks export.
  • INVALID_STATE — a progressive document is still downloading.
  • EXPORT_FAILED — a changed PDF/A file's claim cannot be removed.
  • DOCUMENT_CLOSED — the document has been closed.
TypeScript
const bytes = await doc.save();
// .slice() gives an ArrayBuffer-backed copy, which TypeScript 5.7+ requires of a request body.
await fetch('/upload', { method: 'PUT', body: bytes.slice() });
closemethodsince 1.0.0
close(): Promise<void>
Close the document and free its resources; resolves once they are released. A viewer still open on it refuses undo, redo, snapshot() and Download with a reported DOCUMENT_CLOSED (its exporter rejects with it): destroy the viewer, or open a new document. After Kernstra.destroy() has recovered from an ENGINE_FAILED fault the document is already released, and close() does nothing.
Returns Promise<void> Resolves once its resources are released.
TypeScript
viewer.destroy();
await doc.close();
whenFullyLoadedmethodsince 1.0.0
whenFullyLoaded(): Promise<void>
Only meaningful after fromUrlProgressive(). Resolves once every byte has arrived. Editing, export and search need this, which the background fill reaches shortly after opening. When the download can never finish the document stays open for viewing, and a viewer showing it reports the same once on its error event.
Returns Promise<void> Resolves once every byte has arrived.
  • NETWORK_ERROR — the download can never finish (a range request kept failing through its retries).
  • ABORTED — the signal was aborted.
TypeScript
const doc = await PDFDocument.fromUrlProgressive('/big-report.pdf');
await doc.whenFullyLoaded();
const hits = await doc.text.search('Total');
isPageReadymethodsince 1.0.0
isPageReady(pageIndex: number): boolean
Only meaningful after fromUrlProgressive(). Whether that page's bytes are already available.
ParameterTypeDescription
pageIndexnumberZero-based page index.
Returns boolean
ensurePageAvailablemethodsince 1.0.0
ensurePageAvailable(pageIndex: number): Promise<void>
Only meaningful after fromUrlProgressive(). Fetches the ranges that page needs, and resolves when it can be rendered. The viewer does this automatically for pages and thumbnails.
ParameterTypeDescription
pageIndexnumberZero-based page index.
Returns Promise<void> Resolves when the page can be rendered.
TypeScript
if (!doc.isPageReady(9)) await doc.ensurePageAvailable(9);

Namespaces

pagesgettersince 1.0.0
get pages(): PagesApi
Page operations — insert, delete, move, rotate, duplicate, extract, import. See PagesApi.
Returns PagesApi
TypeScript
await doc.pages.rotate(0, 90);
annotationsgettersince 1.0.0
get annotations(): AnnotationsApi
List, create, update, delete and flatten annotations; read comment threads, reply and set review status. Without the extract feature the contents and author of every annotation and comment, and a web link's uri, come back ''. See AnnotationsApi.
Returns AnnotationsApi
TypeScript
const onPage = await doc.annotations.list(0);
formsgettersince 1.0.0
get forms(): FormsApi
Read and set form field values, with or without a viewer. fields() and getValues() throw FEATURE_NOT_LICENSED without extract; setValues() needs forms. See FormsApi.
Returns FormsApi
TypeScript
await doc.forms.setValues({ 'topmostSubform[0].Page1[0].f1_01[0]': 'Ada Lovelace' });
textgettersince 1.0.0
get text(): TextApi
Read and search the document's text. Both members need the extract feature. See TextApi.
Returns TextApi
TypeScript
const hits = await doc.text.search('Invoice', { matchCase: true });
securitygettersince 1.0.0
get security(): SecurityApi
Encryption and permissions. protect() and removeProtection() need the export feature. See SecurityApi.
Returns SecurityApi
TypeScript
if (doc.security.isEncrypted) console.log(doc.security.permissions);
outlinegettersince 1.0.0
get outline(): OutlineApi
The bookmark tree, read whole and edited in place. Without the extract feature every title get() returns is ''; edits need edit-pages. See OutlineApi.
Returns OutlineApi
TypeScript
const chapters = await doc.outline.get();
attachmentsgettersince 1.0.0
get attachments(): AttachmentsApi
Embedded files — list, read, add, remove. Without the extract feature every name list() returns is '' and data() throws FEATURE_NOT_LICENSED; add() and delete() need edit-pages. See AttachmentsApi.
Returns AttachmentsApi
TypeScript
const files = await doc.attachments.list();
signaturesgettersince 1.0.0
get signatures(): SignaturesApi
The signatures the document declares, metadata only — never validated. Without the extract feature each reason is ''. See SignaturesApi.
Returns SignaturesApi
TypeScript
const signed = (await doc.signatures.list()).some((s) => s.signed);