Pages
List the pages of a document to learn their size and order, then work on them by ref:
const { pageCount, pages } = await doc.pages.list();
await doc.pages.rotate([pages[0].ref], 90); // turn the first page
await doc.pages.delete([pages[pageCount - 1].ref]); // delete the last oneList pages#
doc.pages.list() returns every page in display order. It doesn’t load any page content, so it’s
quick even for a long document.
const { pages } = await doc.pages.list();
for (const page of pages) {
console.log(page.index, page.label, page.size.width, page.size.height, page.rotation);
}| Field | What it is |
|---|---|
index | Position in the document, from 0. Changes when pages move. |
ref | The page’s address. Stays the same when pages move; pass it to every page call. |
label | The page’s label from the PDF ('iii', 'A-1'), or null when the file has none. |
size | { width, height } of the visible area (the crop box) in points, before rotation. |
rotation | 0, 90, 180 or 270: how far the page is turned clockwise when shown. |
userUnit | How large one point is, in multiples of 1/72 inch. 1 for almost every file. |
tabOrder | The order its annotations and form fields take keyboard focus in: 'rows', 'columns', 'structure', 'annotations' or 'widgets'; null when the page doesn’t say. |
boxes | The page’s five boxes: media, crop, bleed, trim and art. See below. |
pdfCropBox | Where the page sits in the PDF file’s own numbers, for tools that use those. See below. |
boxes follows the PDF standard. A box the file leaves out takes its default: the crop box is the
media box, and the bleed, trim and art boxes are the crop box. Every box stays inside the media
box, so a crop box that reaches past it is cut back. crop is what the page shows, and size is
its size.
Each box is { x, y, width, height }, measured in points from the top-left corner of what the
page shows, with y going down. So boxes.crop is always { x: 0, y: 0, width, height }, and a
media box that reaches past the page’s top-left corner has a negative x and y:
{
size: { width: 512, height: 672 },
boxes: {
media: { x: -50, y: -60, width: 612, height: 792 },
crop: { x: 0, y: 0, width: 512, height: 672 },
trim: { x: 10, y: 10, width: 492, height: 652 },
// bleed and art
},
pdfCropBox: { left: 50, bottom: 60, right: 562, top: 732 },
}pdfCropBox is the only value in the PDF file’s own numbers: the crop box where the file places
it, as { left, bottom, right, top } with y going up. You need it only beside a tool that reads
those numbers, such as iText, PDFBox or Acrobat JavaScript. pdfRectOf() and pdfPointOf() turn a
box or point into them, and pageBoxOf() and pagePointOf() turn them back. pdfDestinationOf()
and pageDestinationOf() do the same for a destination, with the crop box of the page it goes to:
import { pdfRectOf } from '@embedpdf/engine';
const inTheFile = pdfRectOf({ x: 50, y: 82, width: 50, height: 50 }, page.pdfCropBox);
// { left: 100, right: 150, bottom: 600, top: 650 }size is the page before it’s turned. For a page at 90 or 270, swap width and height to get
the size it’s shown at:
const { size, rotation } = pages[0];
const turned = rotation === 90 || rotation === 270;
const shown = turned ? { width: size.height, height: size.width } : size;Refs, not positions#
Every call that works on pages takes refs, never indexes. A ref stays valid when pages move, rotate, or when pages before it are inserted or deleted, so you can keep it for as long as the page exists.
const page = doc.page(ref); // a handle for one page: render, text, annotationsdoc.page(ref) makes no request. When you stored a page’s object number yourself,
toPageRef(12) turns it back into a ref. See Refs & identity.
Rotate#
rotate() sets how pages are turned: 0, 90, 180 or 270 degrees clockwise. It sets the
rotation, it doesn’t add to it, so one value works for several pages at once:
await doc.pages.rotate([pageA.ref, pageB.ref], 90);To turn a page a further quarter, add 90 to its current rotation from list(). After 270,
start again at 0.
Rotating changes how the page is shown, not its content, so annotations and text keep their
positions. Renders don’t turn by themselves: pass the page’s rotation to show it turned (see
Turn the page).
Reorder#
reorder(refs, position) takes the pages out and puts them back together, in the order you pass
them, at position: right after or before another page, or at the start or the end.
const { pages } = await doc.pages.list();
const last = pages[pages.length - 1].ref;
await doc.pages.reorder([last], 'start'); // the last page to the front
await doc.pages.reorder([pages[0].ref], { after: pages[1].ref }); // swap the first twoA position names a page, never a number, so it means the same when other pages come or go
before the call lands. The page you name can’t be one of the pages you move (InvalidArg);
one the document doesn’t have fails with NotFound.
Delete#
await doc.pages.delete([page.ref]);A page’s annotations go with it. A document keeps at least one page: deleting every page fails
with InvalidArg.
Insert another PDF#
insert(bytes, position?) copies every page of another PDF into this one, with their
annotations. Without a position, the pages go at the end:
const response = await fetch('/appendix.pdf');
const { insertedPages } = await doc.pages.insert(await response.arrayBuffer());insertedPages holds the refs of the new pages, in order. To merge several files, insert them one
after the other.
Insert blank pages#
insertBlank() adds empty pages of the size you give, in points:
// Two US Letter pages after the first page
const { pages } = await doc.pages.list();
const letter = { width: 612, height: 792 };
await doc.pages.insertBlank({ size: letter, count: 2 }, { after: pages[0].ref });A4 is { width: 595, height: 842 }. count is 1 by default and at most 100 per call.
Extract pages to a new PDF#
extract(refs) returns a new PDF with copies of those pages, in the order you pass them. The
document itself doesn’t change.
const bytes = await doc.pages.extract([pages[0].ref, pages[1].ref]);That makes splitting a document a loop, one file per page:
for (const { ref, index } of pages) {
const bytes = await doc.pages.extract([ref]);
await save(`page-${index + 1}.pdf`, bytes);
}And taking pages from one document into another is extract() then insert():
const bytes = await source.pages.extract([ref]);
await target.pages.insert(bytes, 'start');Flatten a page’s annotations#
Flattening draws a page’s annotations into the page itself and removes them, so they can’t be changed or deleted anymore. Filled-in form fields become plain content the same way.
const { results } = await doc.pages.flatten([page.ref]);
// results: [{ page, status: 'applied' }]Each page’s status is 'applied', or 'unchanged' when it had nothing to flatten. Flattening is
all or nothing: when a page fails, or the call is cancelled, no page changes and the call rejects
with the error. Hidden annotations, popups and annotations without an appearance stay as they are.
Flattening can’t be undone, and it ends undo for every change made before it; see
Changes & undo.
By default the page is flattened as it’s shown on screen. Pass { usage: 'print' } to flatten it
as it prints, which leaves out annotations that don’t print:
await doc.pages.flatten([page.ref], { usage: 'print' });New annotations print unless you create them with print: false; see
Drawing annotations yourself.
To flatten only some annotations, see Drawing annotations yourself.
Permissions#
| Call | Needs |
|---|---|
list() | doc.open |
rotate(), reorder(), delete(), insert(), insertBlank() | doc.pages.assemble |
extract() | doc.download: the pages leave the document |
flatten() | doc.pages.modify and doc.annotate.modify |
See Permissions.
Good to know#
What a change returns#
rotate(), reorder(), delete(), insert(), insertBlank(), setName() and removeName()
return the new page list as layout, the same shape list() returns, so you can replace the list
you hold without asking again:
const { layout } = await doc.pages.reorder([ref], 'start');
showThumbnails(layout.pages);Other code that shows pages can listen for pages.reordered, pages.rotated, pages.deleted,
pages.inserted and pages.flattened instead. See Events.
Inserting#
A password-protected PDF can’t be inserted: insert() fails with DocPasswordRequired. Open it,
unlock it, and extract() its pages instead. Pages you insert get new refs; the pages already in
the document keep theirs.
In the browser, an ArrayBuffer you pass to insert() is transferred to the engine, not copied,
so it’s empty afterwards. Pass a copy to keep yours.
Named pages#
A PDF can give pages names. Acrobat’s stamp files work this way: each page is one stamp, named
identifier=label. The names come with the page list:
const { namedPages } = await doc.pages.list();
// [{ name: 'Approved=Approved', target: { kind: 'page', page: ref } }, …]target is { kind: 'page', page } for a page in the document. A name can also point at a hidden
page template ({ kind: 'template' }), which isn’t a page you can show, or at nothing
({ kind: 'dangling' }) in a file another app made.
Add, change or remove a name with setName() and removeName(). The page itself stays:
await doc.pages.setName({ name: 'Draft=Draft', page: page.ref });
await doc.pages.setName({ name: 'Draft=Concept', page: page.ref, replace: 'Draft=Draft' }); // rename
await doc.pages.removeName({ name: 'Draft=Concept' });Both need doc.pages.assemble. Deleting a page removes its names.
Your feedback goes directly to the documentation team.