---
title: "Engine & handles"
description: "The Engine, DocumentHandle, and PageHandle objects and how they relate."
source: "https://www.embedpdf.com/docs/engine/core-concepts/engine-and-handles"
---

# Engine & handles

The engine exposes three layers of objects: the **engine** itself, a
**document handle** for each open document, and a **page handle** for per-page
work. Each layer narrows the scope of what you operate on.

## The engine

`localEngine()` returns an object implementing the `Engine` interface:

```ts
interface Engine {
  open(input: OpenInput, options?: OpenOptions): AbortablePromise<DocumentHandle>;
  destroy(): AbortablePromise<void>;
}
```

`open()` is the only entry point.

The local engine accepts `{ kind: 'bytes' }` (and `{ kind: 'layerBytes' }`
for layer documents) — you hand it the PDF bytes and a stable id (see
[Getting started](https://www.embedpdf.com/docs/engine/getting-started)).

Call `destroy()` when your app is shutting the engine down; existing handles
should be `close()`d first.

> One engine can hold many open documents at once. When you open by token, each
> handle carries its own per-document bearer, so a single engine can serve
> several independently-authorized documents concurrently.

## The document handle

`open()` resolves to a `DocumentHandle` — your entry point to everything about
one open document:

```ts
interface DocumentHandle {
  readonly id: string;
  readonly capabilities: DocumentCapabilities;
  readonly security: DocumentSecurityService;
  readonly metadata: MetadataService;
  readonly annotations: DocumentAnnotationsService;
  readonly pages: DocumentPagesService;
  readonly render: DocumentRenderService;

  page(pageObjectNumber: PageObjectNumber): PageHandle;
  download(opts?: { mode?: PdfSaveMode }): AbortablePromise<Uint8Array>;
  close(): AbortablePromise<void>;
}
```

- `metadata`, `annotations`, `pages`, and `security` are **document-scoped
  services** (covered in their own pages).
- `render` carries the document's render *policy* — not pixels; per-page
  rendering stays on `page(pon).render`. See
  [Render policy](https://www.embedpdf.com/docs/engine/core-concepts/pages-and-rendering#render-policy).
- `page(pon)` returns a page handle (below).
- `download()` returns the full PDF bytes — see [Downloading](https://www.embedpdf.com/docs/engine/core-concepts/downloading).
- `close()` releases the handle. It's idempotent and safe to call more than
  once.

### Capabilities

`capabilities` advertises engine-specific behavior so portable code can adapt:

```ts
interface DocumentCapabilities {
  readonly weakAnnotationEditSessions: 'not-needed' | 'required';
  readonly pageEditSessions: 'unsupported' | 'supported';
}
```

On the cloud engine, `weakAnnotationEditSessions` is `'required'` (index-based
annotation edits need an active edit session) and `pageEditSessions` is
`'unsupported'`.

## The page handle

`doc.page(pon)` returns a `PageHandle` keyed on the page's **indirect object
number**, never its display index — so the handle stays valid across page
reorders.

```ts
interface PageHandle {
  readonly pageObjectNumber: PageObjectNumber;
  readonly pageIndex: number; // advisory display index at mint time
  readonly annotations: PageAnnotationsService;
  readonly text: PageTextService;
  readonly geometry: PageGeometryService;
  readonly render: PageRenderService;
}
```

`page()` is synchronous — page records are already known to the handle. To
discover page object numbers, call `doc.pages.list()`:

```ts
const { pageCount, pages } = await doc.pages.list();
const firstPon = pages[0].pageObjectNumber;
const page = doc.page(firstPon);
```

> Treat `pageIndex` as advisory metadata. It reflects display order
> at the time the handle was minted; the durable key is always
> `pageObjectNumber`.

## Lifecycle at a glance

```ts
const engine = localEngine();

const doc = await engine.open({ kind: 'bytes', id: 'doc_123', bytes }); // open
const page = doc.page((await doc.pages.list()).pages[0].pageObjectNumber); // work
await page.render.image();

await doc.close();    // release the document
await engine.destroy(); // tear down the engine
```
