# 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()`](https://kernstra.com/docs/web-sdk/api/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.

- Kind: class
- Access: `await PDFDocument.fromUrl(url, options?) | fromFile(file, options?) | fromBytes(bytes, options?) | fromUrlProgressive(url, options?)`

## Usage

```ts
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

### `fromUrl`

```ts
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](https://kernstra.com/docs/web-sdk/guides/document-features).

- Kind: static method
- Since: 1.0.0

| 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. |

Returns `Promise<PDFDocument>`

Throws:

- `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).

```ts
const doc = await PDFDocument.fromUrl('/api/documents/123', {
  headers: { Authorization: 'Bearer ' + token },
});
```

Page: https://kernstra.com/docs/web-sdk/api/pdfdocument/fromurl

### `fromFile`

```ts
static fromFile(file: File, options?: OpenOptions): Promise<PDFDocument>
```

Load a PDF from a `File` object (file picker).

- Kind: static method
- Since: 1.0.0

| Parameter | Type | Description |
| --- | --- | --- |
| `file` | `File` | A `File`, from a file picker or a drop. |
| `options?` | `OpenOptions` | `{ password? }` — the password of an encrypted file. |

Returns `Promise<PDFDocument>`

Throws:

- `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).

```ts
input.addEventListener('change', async () => {
  const [file] = input.files ?? [];
  if (file) doc = await PDFDocument.fromFile(file);
});
```

Page: https://kernstra.com/docs/web-sdk/api/pdfdocument/fromfile

### `fromBytes`

```ts
static fromBytes(bytes: Uint8Array, options?: OpenOptions): Promise<PDFDocument>
```

Load a PDF from a `Uint8Array`.

- Kind: static method
- Since: 1.0.0

| Parameter | Type | Description |
| --- | --- | --- |
| `bytes` | `Uint8Array` | The PDF bytes. |
| `options?` | `OpenOptions` | `{ password? }` — the password of an encrypted file. |

Returns `Promise<PDFDocument>`

Throws:

- `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).

```ts
const bytes = new Uint8Array(await (await fetch('/terms.pdf')).arrayBuffer());
const doc = await PDFDocument.fromBytes(bytes);
```

Page: https://kernstra.com/docs/web-sdk/api/pdfdocument/frombytes

### `fromUrlProgressive`

```ts
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).

- Kind: static method
- Since: 1.0.0

| 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`). |

Returns `Promise<PDFDocument>`

Throws:

- `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.

```ts
const doc = await PDFDocument.fromUrlProgressive('/big-report.pdf', {
  chunkSize: 256 * 1024, // range-request size (default 256 KiB)
  onProgress: (loaded, total) => updateProgressBar(loaded / total),
});
```

Page: https://kernstra.com/docs/web-sdk/api/pdfdocument/fromurlprogressive

## Properties

### `pageCount`

```ts
get pageCount(): number
```

Total number of pages.

- Kind: getter
- Since: 1.0.0

Returns `number`

Throws:

- `DOCUMENT_CLOSED` — read on a closed document.

```ts
const last = doc.pageCount - 1;
```

Page: https://kernstra.com/docs/web-sdk/api/pdfdocument/pagecount

### `metadata`

```ts
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 `''`.

- Kind: getter
- Since: 1.0.0

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).

Throws:

- `DOCUMENT_CLOSED` — read on a closed document.

```ts
document.title = doc.metadata.title || file.name;
```

Page: https://kernstra.com/docs/web-sdk/api/pdfdocument/metadata

### `pdfA`

```ts
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.

- Kind: getter
- Since: 1.0.0

Returns `PDFAClaim | null`: `{ part, conformance? }`, or `null`.

Throws:

- `DOCUMENT_CLOSED` — read on a closed document.

```ts
if (doc.pdfA) console.log('PDF/A-' + doc.pdfA.part + (doc.pdfA.conformance ?? ''));
```

Page: https://kernstra.com/docs/web-sdk/api/pdfdocument/pdfa

### `hasPassword`

```ts
get hasPassword(): boolean
```

Whether the document was opened with a password.

- Kind: getter
- Since: 1.0.0

Returns `boolean`

Page: https://kernstra.com/docs/web-sdk/api/pdfdocument/haspassword

### `isClosed`

```ts
get isClosed(): boolean
```

Whether `close()` has been called.

- Kind: getter
- Since: 1.0.0

Returns `boolean`

Page: https://kernstra.com/docs/web-sdk/api/pdfdocument/isclosed

### `containsJavaScript`

```ts
get containsJavaScript(): boolean
```

The document declares embedded JavaScript. The SDK never executes it; use this to screen untrusted uploads or warn the user.

- Kind: getter
- Since: 1.0.0

Returns `boolean`

Throws:

- `DOCUMENT_CLOSED` — read on a closed document.

```ts
if (doc.containsJavaScript) showWarning('This file contains scripts');
```

Page: https://kernstra.com/docs/web-sdk/api/pdfdocument/containsjavascript

### `isProgressive`

```ts
get isProgressive(): boolean
```

Opened via `fromUrlProgressive()`.

- Kind: getter
- Since: 1.0.0

Returns `boolean`

Page: https://kernstra.com/docs/web-sdk/api/pdfdocument/isprogressive

### `isFullyLoaded`

```ts
get isFullyLoaded(): boolean
```

All bytes have arrived. Always `true` for non-progressive documents.

- Kind: getter
- Since: 1.0.0

Returns `boolean`

Page: https://kernstra.com/docs/web-sdk/api/pdfdocument/isfullyloaded

### `loadedByteLength`

```ts
get loadedByteLength(): number
```

The file's size in bytes, as opened.

- Kind: getter
- Since: 1.0.0

Returns `number`

Page: https://kernstra.com/docs/web-sdk/api/pdfdocument/loadedbytelength

## Lifecycle

### `save`

```ts
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()`](https://kernstra.com/docs/web-sdk/api/pdfviewer/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.

- Kind: method
- Since: 1.0.0

Returns `Promise<Uint8Array>`: The document's bytes.

Throws:

- `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.

```ts
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() });
```

Page: https://kernstra.com/docs/web-sdk/api/pdfdocument/save

### `close`

```ts
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.

- Kind: method
- Since: 1.0.0

Returns `Promise<void>`: Resolves once its resources are released.

```ts
viewer.destroy();
await doc.close();
```

Page: https://kernstra.com/docs/web-sdk/api/pdfdocument/close

### `whenFullyLoaded`

```ts
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.

- Kind: method
- Since: 1.0.0

Returns `Promise<void>`: Resolves once every byte has arrived.

Throws:

- `NETWORK_ERROR` — the download can never finish (a range request kept failing through its retries).
- `ABORTED` — the `signal` was aborted.

```ts
const doc = await PDFDocument.fromUrlProgressive('/big-report.pdf');
await doc.whenFullyLoaded();
const hits = await doc.text.search('Total');
```

Page: https://kernstra.com/docs/web-sdk/api/pdfdocument/whenfullyloaded

### `isPageReady`

```ts
isPageReady(pageIndex: number): boolean
```

Only meaningful after `fromUrlProgressive()`. Whether that page's bytes are already available.

- Kind: method
- Since: 1.0.0

| Parameter | Type | Description |
| --- | --- | --- |
| `pageIndex` | `number` | Zero-based page index. |

Returns `boolean`

Page: https://kernstra.com/docs/web-sdk/api/pdfdocument/ispageready

### `ensurePageAvailable`

```ts
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.

- Kind: method
- Since: 1.0.0

| Parameter | Type | Description |
| --- | --- | --- |
| `pageIndex` | `number` | Zero-based page index. |

Returns `Promise<void>`: Resolves when the page can be rendered.

```ts
if (!doc.isPageReady(9)) await doc.ensurePageAvailable(9);
```

Page: https://kernstra.com/docs/web-sdk/api/pdfdocument/ensurepageavailable

## Namespaces

### `pages`

```ts
get pages(): PagesApi
```

Page operations — insert, delete, move, rotate, duplicate, extract, import. See [`PagesApi`](https://kernstra.com/docs/web-sdk/api/doc-pages).

- Kind: getter
- Since: 1.0.0

Returns `PagesApi`

```ts
await doc.pages.rotate(0, 90);
```

Page: https://kernstra.com/docs/web-sdk/api/pdfdocument/pages

### `annotations`

```ts
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`](https://kernstra.com/docs/web-sdk/api/doc-annotations).

- Kind: getter
- Since: 1.0.0

Returns `AnnotationsApi`

```ts
const onPage = await doc.annotations.list(0);
```

Page: https://kernstra.com/docs/web-sdk/api/pdfdocument/annotations

### `forms`

```ts
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`](https://kernstra.com/docs/web-sdk/api/doc-forms).

- Kind: getter
- Since: 1.0.0

Returns `FormsApi`

```ts
await doc.forms.setValues({ 'topmostSubform[0].Page1[0].f1_01[0]': 'Ada Lovelace' });
```

Page: https://kernstra.com/docs/web-sdk/api/pdfdocument/forms

### `text`

```ts
get text(): TextApi
```

Read and search the document's text. Both members need the `extract` feature. See [`TextApi`](https://kernstra.com/docs/web-sdk/api/doc-text).

- Kind: getter
- Since: 1.0.0

Returns `TextApi`

```ts
const hits = await doc.text.search('Invoice', { matchCase: true });
```

Page: https://kernstra.com/docs/web-sdk/api/pdfdocument/text

### `security`

```ts
get security(): SecurityApi
```

Encryption and permissions. `protect()` and `removeProtection()` need the `export` feature. See [`SecurityApi`](https://kernstra.com/docs/web-sdk/api/doc-security).

- Kind: getter
- Since: 1.0.0

Returns `SecurityApi`

```ts
if (doc.security.isEncrypted) console.log(doc.security.permissions);
```

Page: https://kernstra.com/docs/web-sdk/api/pdfdocument/security

### `outline`

```ts
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`](https://kernstra.com/docs/web-sdk/api/doc-outline).

- Kind: getter
- Since: 1.0.0

Returns `OutlineApi`

```ts
const chapters = await doc.outline.get();
```

Page: https://kernstra.com/docs/web-sdk/api/pdfdocument/outline

### `attachments`

```ts
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`](https://kernstra.com/docs/web-sdk/api/doc-attachments).

- Kind: getter
- Since: 1.0.0

Returns `AttachmentsApi`

```ts
const files = await doc.attachments.list();
```

Page: https://kernstra.com/docs/web-sdk/api/pdfdocument/attachments

### `signatures`

```ts
get signatures(): SignaturesApi
```

The signatures the document declares, metadata only — never validated. Without the `extract` feature each `reason` is `''`. See [`SignaturesApi`](https://kernstra.com/docs/web-sdk/api/doc-signatures).

- Kind: getter
- Since: 1.0.0

Returns `SignaturesApi`

```ts
const signed = (await doc.signatures.list()).some((s) => s.signed);
```

Page: https://kernstra.com/docs/web-sdk/api/pdfdocument/signatures

Web page: https://kernstra.com/docs/web-sdk/api/pdfdocument
