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.
Usage
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
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.| Parameter | Type | Description |
|---|---|---|
| url | string | The 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. |
Promise<PDFDocument> ABORTED— thesignalwas aborted.CORS_ERROR— the server did not allow the page's origin.NETWORK_ERROR— the request failed, including401and403.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).
const doc = await PDFDocument.fromUrl('/api/documents/123', { headers: { Authorization: 'Bearer ' + token }, });
File object (file picker).| Parameter | Type | Description |
|---|---|---|
| file | File | A File, from a file picker or a drop. |
| options? | OpenOptions | { password? } — the password of an encrypted file. |
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).
input.addEventListener('change', async () => { const [file] = input.files ?? []; if (file) doc = await PDFDocument.fromFile(file); });
Uint8Array.| Parameter | Type | Description |
|---|---|---|
| bytes | Uint8Array | The PDF bytes. |
| options? | OpenOptions | { password? } — the password of an encrypted file. |
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).
const bytes = new Uint8Array(await (await fetch('/terms.pdf')).arrayBuffer()); const doc = await PDFDocument.fromBytes(bytes);
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).
| Parameter | Type | Description |
|---|---|---|
| url | string | The URL to open over HTTP range requests. |
| options? | OpenProgressiveOptions | What 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). |
Promise<PDFDocument> ABORTED— thesignalwas 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.
const doc = await PDFDocument.fromUrlProgressive('/big-report.pdf', { chunkSize: 256 * 1024, // range-request size (default 256 KiB) onProgress: (loaded, total) => updateProgressBar(loaded / total), });
Properties
number DOCUMENT_CLOSED— read on a closed document.
const last = doc.pageCount - 1;
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 ''.
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.
document.title = doc.metadata.title || file.name;
{ 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.PDFAClaim | null { part, conformance? }, or null.DOCUMENT_CLOSED— read on a closed document.
if (doc.pdfA) console.log('PDF/A-' + doc.pdfA.part + (doc.pdfA.conformance ?? ''));
boolean close() has been called.boolean boolean DOCUMENT_CLOSED— read on a closed document.
if (doc.containsJavaScript) showWarning('This file contains scripts');
fromUrlProgressive().boolean true for non-progressive documents.boolean number Lifecycle
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.
Promise<Uint8Array> The document's bytes.FEATURE_NOT_LICENSED— the license lacksexport.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.
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() });
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.Promise<void> Resolves once its resources are released.viewer.destroy(); await doc.close();
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.Promise<void> Resolves once every byte has arrived.NETWORK_ERROR— the download can never finish (a range request kept failing through its retries).ABORTED— thesignalwas aborted.
const doc = await PDFDocument.fromUrlProgressive('/big-report.pdf'); await doc.whenFullyLoaded(); const hits = await doc.text.search('Total');
fromUrlProgressive(). Whether that page's bytes are already available.| Parameter | Type | Description |
|---|---|---|
| pageIndex | number | Zero-based page index. |
boolean fromUrlProgressive(). Fetches the ranges that page needs, and resolves when it can be rendered. The viewer does this automatically for pages and thumbnails.| Parameter | Type | Description |
|---|---|---|
| pageIndex | number | Zero-based page index. |
Promise<void> Resolves when the page can be rendered.if (!doc.isPageReady(9)) await doc.ensurePageAvailable(9);
Namespaces
PagesApi.PagesApi await doc.pages.rotate(0, 90);
extract feature the contents and author of every annotation and comment, and a web link's uri, come back ''. See AnnotationsApi.AnnotationsApi const onPage = await doc.annotations.list(0);
fields() and getValues() throw FEATURE_NOT_LICENSED without extract; setValues() needs forms. See FormsApi.FormsApi await doc.forms.setValues({ 'topmostSubform[0].Page1[0].f1_01[0]': 'Ada Lovelace' });
extract feature. See TextApi.TextApi const hits = await doc.text.search('Invoice', { matchCase: true });
protect() and removeProtection() need the export feature. See SecurityApi.SecurityApi if (doc.security.isEncrypted) console.log(doc.security.permissions);
extract feature every title get() returns is ''; edits need edit-pages. See OutlineApi.OutlineApi const chapters = await doc.outline.get();
extract feature every name list() returns is '' and data() throws FEATURE_NOT_LICENSED; add() and delete() need edit-pages. See AttachmentsApi.AttachmentsApi const files = await doc.attachments.list();
extract feature each reason is ''. See SignaturesApi.SignaturesApi const signed = (await doc.signatures.list()).some((s) => s.signed);