EmbedPDF

Organizing pages

The page edit plugin changes the document’s pages: rotate, reorder, delete, duplicate, and insert new ones, blank or from another PDF. These are edits to the file, kept when it’s downloaded. Register pageEditPlugin() and use usePageEdit():

Loading live preview…

<script setup lang="ts">
import { DocumentGate, Viewer } from '@embedpdf/vue/runtime';
import type { OpenInput } from '@embedpdf/vue/runtime';
import { renderPlugin } from '@embedpdf/vue/render';
import { pageEditPlugin } from '@embedpdf/vue/page-edit';
import { localEngine } from '@embedpdf/engine';
import PageOrganizer from './PageOrganizer.vue';

import '../organize.css';

const engine = localEngine();
const plugins = [renderPlugin(), pageEditPlugin()];

const welcome = async (): Promise<OpenInput> => {
  const response = await fetch('/pdfs/welcome.pdf');
  return { kind: 'bytes', id: 'welcome', bytes: new Uint8Array(await response.arrayBuffer()) };
};
</script>

<template>
  <Viewer :engine="engine" :plugins="plugins" :initial-documents="[{ source: welcome }]">
    <DocumentGate>
      <template #fallback><p class="loading">Loading…</p></template>
      <PageOrganizer />
    </DocumentGate>
  </Viewer>
</template>
await pageEdit.rotateBy([page.ref], 90);
await pageEdit.reorder([page.ref], 'start'); // to the front
await pageEdit.delete([page.ref]);

After an edit, everything that shows pages updates by itself: the Stage, thumbnails, page numbers. On a shared document, the others see the change too. Each edit is one step to undo: undo puts the pages back as they were, and brings a deleted page back with its annotations.

Calls that act on pages take a list of them, so one call works for a single page and for a multi-selection: the pages in selected.

Rotate pages#

await pageEdit.rotateBy([page.ref], 90); // a quarter turn clockwise from where it is
await pageEdit.rotateBy([page.ref], -90); // and back
await pageEdit.setRotation([pageA.ref, pageB.ref], 180); // the same rotation for several

Loading live preview…

<script setup lang="ts">
import { DocumentGate, Viewer } from '@embedpdf/vue/runtime';
import type { OpenInput } from '@embedpdf/vue/runtime';
import { Stage, stagePlugin } from '@embedpdf/vue/stage';
import { RenderLayer, renderPlugin } from '@embedpdf/vue/render';
import { pageEditPlugin } from '@embedpdf/vue/page-edit';
import { localEngine } from '@embedpdf/engine';
import RotateToolbar from './RotateToolbar.vue';

import '../rotate.css';

const engine = localEngine();
const plugins = [stagePlugin(), renderPlugin(), pageEditPlugin()];

const welcome = async (): Promise<OpenInput> => {
  const response = await fetch('/pdfs/welcome.pdf');
  return { kind: 'bytes', id: 'welcome', bytes: new Uint8Array(await response.arrayBuffer()) };
};
</script>

<template>
  <Viewer :engine="engine" :plugins="plugins" :initial-documents="[{ source: welcome }]">
    <DocumentGate>
      <template #fallback><p class="loading">Loading…</p></template>
      <RotateToolbar />
      <Stage class="stage">
        <template #page><RenderLayer /></template>
      </Stage>
    </DocumentGate>
  </Viewer>
</template>

This rotates the page in the file. To only turn how pages are shown, use the Stage’s view rotation. Keep the two on different buttons.

Reorder pages#

reorder() takes the pages and where they go:

await pageEdit.reorder([page.ref], 'start'); // to the front
await pageEdit.reorder([page.ref], 'end'); // to the back
await pageEdit.reorder([pageA.ref, pageB.ref], { after: pageC.ref }); // after another page
await pageEdit.reorder([page.ref], { before: pageC.ref });

Several pages keep their order and land together.

Loading live preview…

<script setup lang="ts">
import { DocumentGate, Viewer } from '@embedpdf/vue/runtime';
import type { OpenInput } from '@embedpdf/vue/runtime';
import { renderPlugin } from '@embedpdf/vue/render';
import { pageEditPlugin } from '@embedpdf/vue/page-edit';
import { localEngine } from '@embedpdf/engine';
import PageOrder from './PageOrder.vue';

import '../reorder.css';

const engine = localEngine();
const plugins = [renderPlugin(), pageEditPlugin()];

const welcome = async (): Promise<OpenInput> => {
  const response = await fetch('/pdfs/welcome.pdf');
  return { kind: 'bytes', id: 'welcome', bytes: new Uint8Array(await response.arrayBuffer()) };
};
</script>

<template>
  <Viewer :engine="engine" :plugins="plugins" :initial-documents="[{ source: welcome }]">
    <DocumentGate>
      <template #fallback><p class="loading">Loading…</p></template>
      <PageOrder />
    </DocumentGate>
  </Viewer>
</template>

Delete pages#

await pageEdit.delete([pageA.ref, pageB.ref]);

A document always keeps at least one page: deleting every page is refused.

Insert pages#

// A blank page after this one, the size of an A4 sheet
await pageEdit.insertBlank({ position: { after: page.ref }, size: { width: 595, height: 842 } });
 
// Pages from another PDF: all of them, or the ones you name
await pageEdit.insertFromBytes(otherPdf, { position: 'end' });
await pageEdit.insertFromBytes(otherPdf, { pageIndexes: [0, 2], position: 'start' });
 
// Pages from another document open in the viewer
await pageEdit.insertFromDocument(otherDocumentId, [pageRef], { position: 'end' });
 
// Copies of pages, right after them
await pageEdit.duplicate([page.ref]);

Each resolves with { pages }: the new pages, so you can scroll to them or select them. Without a size, a blank page takes the size of the page it goes next to.

Loading live preview…

<script setup lang="ts">
import { DocumentGate, Viewer } from '@embedpdf/vue/runtime';
import type { OpenInput } from '@embedpdf/vue/runtime';
import { Stage, stagePlugin } from '@embedpdf/vue/stage';
import { RenderLayer, renderPlugin } from '@embedpdf/vue/render';
import { pageEditPlugin } from '@embedpdf/vue/page-edit';
import { localEngine } from '@embedpdf/engine';
import InsertToolbar from './InsertToolbar.vue';

import '../insert.css';

const engine = localEngine();
const plugins = [stagePlugin(), renderPlugin(), pageEditPlugin()];

const welcome = async (): Promise<OpenInput> => {
  const response = await fetch('/pdfs/welcome.pdf');
  return { kind: 'bytes', id: 'welcome', bytes: new Uint8Array(await response.arrayBuffer()) };
};
</script>

<template>
  <Viewer :engine="engine" :plugins="plugins" :initial-documents="[{ source: welcome }]">
    <DocumentGate>
      <template #fallback><p class="loading">Loading…</p></template>
      <InsertToolbar />
      <Stage class="stage">
        <template #page><RenderLayer /></template>
      </Stage>
    </DocumentGate>
  </Viewer>
</template>

Extract pages#

extract() gives you some pages as a new PDF, without changing this document:

const bytes = await pageEdit.extract([pageA.ref, pageB.ref]);
saveFile(bytes, 'chapter-2.pdf');

Loading live preview…

<script setup lang="ts">
import { DocumentGate, Viewer } from '@embedpdf/vue/runtime';
import type { OpenInput } from '@embedpdf/vue/runtime';
import { renderPlugin } from '@embedpdf/vue/render';
import { pageEditPlugin } from '@embedpdf/vue/page-edit';
import { localEngine } from '@embedpdf/engine';
import ExtractPages from './ExtractPages.vue';

import '../extract.css';

const engine = localEngine();
const plugins = [renderPlugin(), pageEditPlugin()];

const welcome = async (): Promise<OpenInput> => {
  const response = await fetch('/pdfs/welcome.pdf');
  return { kind: 'bytes', id: 'welcome', bytes: new Uint8Array(await response.arrayBuffer()) };
};
</script>

<template>
  <Viewer :engine="engine" :plugins="plugins" :initial-documents="[{ source: welcome }]">
    <DocumentGate>
      <template #fallback><p class="loading">Loading…</p></template>
      <ExtractPages />
    </DocumentGate>
  </Viewer>
</template>

duplicate() and extract() copy the pages as they show, with the edits made before them. A duplicate’s copies bring the pages’ annotations, and their form fields as fields of their own: a name the document already uses gets the first free name_2, so filling in a copy leaves the original alone.

Select pages#

select(pages) makes pages the selection, and selected lists them, in the order they were selected. The selection is the reader’s: show it, and act on it with your own buttons. It holds refs, so it names the same pages after a reorder, and a page that’s deleted leaves it.

pageEdit.select([page.ref]);
await pageEdit.rotateBy(selected, 90);

Where a page goes#

PositionMeans
{ after: pageRef }Right after that page
{ before: pageRef }Right before that page
'start'Before the first page
'end'After the last page

A position names a neighbour by its ref, not a number, so it stays right when pages move before the edit applies.

Methods#

Everything usePageEdit() gives you.

MethodWhat it does
select(pages)Makes these pages the selection; [] clears it. Select pages
rotateBy(pages, delta)Turns each page by 90, -90 or 180 degrees from where it is. Rotate pages
setRotation(pages, rotation)Gives every page the same rotation: 0, 90, 180 or 270
reorder(pages, position)Moves pages to a new place. Several pages keep their order and land together. Reorder pages
delete(pages)Deletes pages. Deleting every page is refused. Delete pages
insertBlank({ position?, size?, count? })Inserts blank pages, one unless you pass a count. Resolves { pages }, the new pages. Insert pages
insertFromBytes(bytes, { pageIndexes?, position? })Inserts the pages of another PDF, or only the ones at pageIndexes. Resolves { pages }.
insertFromDocument(documentId, pages, { position? })Inserts copies of pages from another document open in the viewer. Resolves { pages }.
duplicate(pages, { position? })Copies pages, right after the last of them unless you pass a position. Resolves { pages }.
extract(pages)Copies pages into a new PDF and resolves its bytes. This document doesn’t change. Extract pages
canEdit(), canExtract()Whether rotating, reordering, deleting and inserting are allowed, and whether copying pages out is. Permissions

position is one of the values in Where a page goes; without one, new pages go at the end. Every method that returns a promise also takes a signal in its options, to cancel it.

State#

Everything usePageEditState() gives you, as refs: your template updates when a value changes. The method in the last column reads the same value once.

FieldWhat it isRead once
selectedThe selected pages, in the order they were selectedgetSelected()
canEditWhether rotating, reordering, deleting and inserting pages is allowedcanEdit()
canExtractWhether copying pages out is allowedcanExtract()

Events#

Page changes are document events, so every part of your app hears them, whichever plugin made the change:

page-count.vue
<script setup lang="ts">
import { ref } from 'vue';
import { useDocumentsEvent } from '@embedpdf/vue/runtime';

const pageCount = ref(0);

useDocumentsEvent(
  (documents) => documents.onPagesChanged,
  ({ pages }) => (pageCount.value = pages.length),
);
</script>

<template>
  <span>{{ pageCount }} pages</span>
</template>

The page edit plugin has no events of its own. onPagesChanged and what it carries are listed under Opening documents.

Permissions#

PermissionWhat it allowsCheck
doc.pages.assembleRotate, reorder, delete, duplicate and insert pagespageEdit.canEdit()
doc.downloadextract() and insertFromDocument(), which copy pages outpageEdit.canExtract()

Good to know#

Pages have a ref, not just a number#

Each page has a ref: its identity in the file. A page’s index changes the moment a page before it moves or goes; its ref never does. So a call still hits the page the user clicked, even if the document changed in between. You get refs from usePageList() (page.ref), and anything that points at a page carries one: a search result’s page, an annotation’s page.

Undo, and when an edit shows#

Each edit is one step to undo, so an undo button undoes the last of them, page edits and annotation edits alike. Undo puts the pages back as they were: a moved page goes back to its place, a deleted page comes back under the same ref with its annotations, and inserted pages go. A page someone changed since is left alone.

A turn, a move, a delete, a blank page or a duplicate shows at once, before the engine has it, and so does undoing one: on a server there’s no round trip to wait for. If the engine refuses an edit, the pages go back as they were. Pages of another PDF or document show once the engine has them. Quick edits add up: two quarter turns in a row are a half turn.

Page labels#

A page’s label ('iii', 'A-1') follows the page through edits, the way the engine numbers it: a page takes the numbering of the section it lands in, so a page inserted after the front matter is numbered as front matter, and deleting a section’s first page makes the next page its first. The labels show at once with the edit, and undo puts them back. The sections themselves are pageLabels on the document; see Page labels in the engine docs for the rules.

What moves with a page#

Annotations, links and form fields belong to their page, so they move, rotate and go with it. Rotation leaves annotations where they are on the page; they turn with it.

Page edits are saved like every other change; see Saving & downloading.

Was this page helpful?

Your feedback goes directly to the documentation team.