EmbedPDF

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 one

List 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);
}
FieldWhat it is
indexPosition in the document, from 0. Changes when pages move.
refThe page’s address. Stays the same when pages move; pass it to every page call.
labelThe 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.
rotation0, 90, 180 or 270: how far the page is turned clockwise when shown.
userUnitHow large one point is, in multiples of 1/72 inch. 1 for almost every file.
tabOrderThe order its annotations and form fields take keyboard focus in: 'rows', 'columns', 'structure', 'annotations' or 'widgets'; null when the page doesn’t say.
boxesThe page’s five boxes: media, crop, bleed, trim and art. See below.
pdfCropBoxWhere 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, annotations

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

A 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#

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

Was this page helpful?

Your feedback goes directly to the documentation team.