Pages & rendering
Page work splits across two services: the document-scoped pages service for
listing and structure edits, and the per-page render service for producing
pixels.
Listing pages#
doc.pages.list() returns a geometry snapshot of every page in display order.
It never loads page content, so it’s cheap.
const { pageCount, pages } = await doc.pages.list();
for (const page of pages) {
console.log(page.index, page.pageObjectNumber, page.label, page.width, page.height, page.rotation);
}Each entry is a PageLayout: display index, durable pageObjectNumber,
label (or null), un-rotated crop width/height in PDF points, rotation,
userUnit, and the raw PDF boxes (media, crop, and any declared
bleed/trim/art).
Sizes are un-rotated. For 90°/270° pages, swap width and height yourself to get the on-screen dimensions — the display transform lives in your code, not in the wire data.
Rendering a page#
Get a PageHandle and call its render service. There are two methods:
interface PageRenderService {
image(options?: PageImageOptions): AbortablePromise<PageImageHandle>;
raw(options?: PageRenderOptions): AbortablePromise<PageRaster>;
}Encoded images#
render.image() returns an encoded image (PNG or WebP) — ideal for <img>
elements and HTTP caching.
const page = doc.page(pageObjectNumber);
const image = await page.render.image({
viewport: { kind: 'width', width: 1400 },
format: 'webp',
quality: 0.9,
background: 'white',
});
const { url, revoke } = await image.objectUrl();
imgEl.src = url;
// later, when the image is no longer shown:
revoke();The returned PageImageHandle carries format, contentType, optional
width/height, a source (bytes or url), and the objectUrl() helper
that builds (and lets you revoke) a browser object URL.
Raw rasters (local engine only)#
For canvas compositing or custom pipelines, render.raw() returns a PageRaster
with an RGBA ArrayBuffer. This is local/WASM only — the cloud engine serves
encoded images, so use render.image() there.
const raster = await page.render.raw({ viewport: { kind: 'scale', scale: 2 } });
// raster.width, raster.height, raster.stride, raster.data (ArrayBuffer, rgba8)
const pixels = new Uint8ClampedArray(raster.data);Render options#
Both methods accept the same core options (image() adds format and
quality):
| Option | Type | Default | Notes |
|---|---|---|---|
viewport | { kind: 'scale', scale? } or { kind: 'width', width } | scale 1 | scale renders one PDF point as N device pixels (fold in devicePixelRatio yourself); width sets an exact output width and preserves aspect. |
target | { kind: 'page' } or { kind: 'rect', rect } | whole page | Render a sub-rectangle in PDF user space (origin bottom-left, top > bottom). |
rotation | 0 | 90 | 180 | 270 | page rotation | Extra rotation applied on top of the page’s own. |
background | 'white' | 'transparent' | 'white' | Use transparent with PNG/WebP for alpha. |
includeAnnotations | boolean | server default | Whether annotations are baked into the raster. |
format | 'png' | 'webp' | 'bmp' | server default | image() only. Network endpoints serve png/webp. |
quality | number | — | image() only; lossy-format quality. |
Prefer viewport: { kind: ‘width’ } when you’re filling a
known layout column — you get crisp output at exactly the pixel width you need,
and the result is cache-friendly on the server’s CDN.
Render policy#
doc.render.policy() answers what render parameters this deployment treats
as first-class:
const policy = await doc.render.policy();
// { kind: 'continuous' } — or a lattice, see belowThe local engine always answers { kind: 'continuous' }: rendering happens
in-process, there is no shared cache to protect, and any viewport renders
exactly as requested. Exactness is the local product promise — the render
plugin builds on it to keep resting pixels 1:1 with the screen.
The render plugin consumes this policy for you — it conforms every ask,
caches by the resulting identity, and adds deep-zoom tiles past the ladder’s
top. If you render through the plugin (<RenderLayer />, renderPage), you
never call policy() yourself. See the
Render plugin page.
Reordering pages#
doc.pages.move(pageObjectNumbers, destIndex) detaches the listed pages and
re-inserts them as a contiguous block at destIndex (in the post-removal index
space), preserving the order you pass them.
// Move pages 7 and 9 so they sit starting at display index 0.
await doc.pages.move([7, 9], 0);Per-page revision tokens survive a move, so index-based annotation references you’re holding remain valid across a reorder.
Rotating and deleting pages#
rotate sets the ABSOLUTE display rotation of one or more pages — one value
for all, the multi-select gesture. It is pure presentation metadata: content
coordinates are normalized, so cached renders and annotation refs survive
untouched. (The relative “+90° from here” button belongs in the page-edit
plugin, which derives the absolute value for you.)
await doc.pages.rotate([pon], 90); // 0 | 90 | 180 | 270delete removes pages by identity. Deleting every page is rejected — a
document keeps at least one — and deleted PONs are retired, never recycled;
surviving pages keep their identity and revisions.
await doc.pages.delete([pon]);Adding pages#
Two verbs, one result shape. insert copies every page of a standalone PDF
(bytes) in at destIndex (omitted → append) — the merge/import path.
insertBlank creates blank pages of an explicit size (PDF points), no source
document needed:
// Append every page of another PDF.
const merged = await doc.pages.insert!(bytes);
// Two blank US-Letter pages at display index 1.
const added = await doc.pages.insertBlank!(
{ size: { width: 612, height: 792 }, count: 2 },
1,
);
added.insertedPageObjectNumbers; // fresh PONs, in insertion order
added.layout; // the full new layout — same shape as pages.list()The inserted pages get fresh, never-recycled object numbers; every
pre-existing page keeps its identity and revisions — an insert never
invalidates refs on its neighbours. Both verbs (and extract, below) are
required members of the contract, implemented identically by the local and
cloud engines.
Extracting pages#
extract is the read that turns pages into a portable asset: the given
pages, in the order you pass them, serialized as a standalone PDF. The source
document is untouched — nothing changes, no event fires.
const bytes = await doc.pages.extract!([ponA, ponB]);
// …hand to pages.insert on another document, or download it.Structure verbs are gated by the document’s doc.pages.assemble capability
(PDF permission bit 11); extract egresses content, so it is gated by
doc.download instead.
Your feedback goes directly to the documentation team.