Copy PDF Pages in React with JavaScript

Adding the cover of a product manual to the front of a project description, merging pages 2 and 3 of a quotation into a contract, combining several reports into one summary — all of it comes down to moving pages from one PDF into another. Desktop software means opening two windows and dragging back and forth, and one wrong drop means starting over; with more pages, the resulting order is easy to get wrong. A different kind of trouble is mismatched page sizes: the cover is A5 and the target document is A4, so moving the page across as-is leaves a band of white space around it.

Spire.PDF for JavaScript loads, modifies and saves PDF documents directly in the browser based on WebAssembly, so the whole copying process runs locally and reads and writes files through a virtual file system (VFS), with no backend service required.

This article covers four core features. The first three move whole pages, carrying the source page's size, rotation and margins over as they are; the fourth takes only the page content, and how large a page it is drawn onto is up to you.

Move whole pages

Copy content (templates)

For installation and project configuration, refer to Integrating Spire.PDF for JavaScript in a React Project. The following examples assume Spire.PDF is installed and the WebAssembly module has been initialized.


Copy a Single Page to a Specific Position

Spire.PDF for JavaScript provides the PdfDocument.InsertPage method, which copies a page from another document into the current one and lets you choose which position it lands at. Leave the target index out and the page is appended to the end.

function App() {
  const copyPageAtPosition = async () => {
    // Get the Spire.PDF WASM module
    const pdfModule = window.wasmModule?.spirepdf;

    // Check whether the module is ready
    if (!pdfModule) {
      alert('Spire.PDF is not ready yet');
      return;
    }

    // Load both the source and the target document into the VFS
    const sourceFileName = 'SourceDocument.pdf';
    const targetFileName = 'TargetDocument.pdf';
    await window.spire.FetchFileToVFS(sourceFileName, "", `${process.env.PUBLIC_URL}/data/`);
    await window.spire.FetchFileToVFS(targetFileName, "", `${process.env.PUBLIC_URL}/data/`);

    // Load the two documents
    const sourceDoc = new pdfModule.PdfDocument();
    sourceDoc.LoadFromFile(sourceFileName);
    const targetDoc = new pdfModule.PdfDocument();
    targetDoc.LoadFromFile(targetFileName);

    // Copy page 1 of the source document to the front of the target document
    // pageIndex comes from the source document, resultPageIndex is where the copy lands
    targetDoc.InsertPage({ ldDoc: sourceDoc, pageIndex: 0, resultPageIndex: 0 });

    // Save the result document
    const outputFileName = 'CopyPageAtPosition.pdf';
    targetDoc.SaveToFile(outputFileName);
    sourceDoc.Close();
    targetDoc.Close();

    // Read the generated file from the VFS and trigger the download
    const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([fileArray], { type: 'application/pdf' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = outputFileName;
    a.click();
    URL.revokeObjectURL(url);
  };

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>Copy Page at Position</h1>
      <button onClick={copyPageAtPosition}>
        Start
      </button>
    </div>
  );
}

export default App;

resultPageIndex is the only place among the four features where you control where the page lands: 0 puts it first, 1 puts it second, and passing the current page count is the same as appending it.

Page 1 of the source document now sits in front of the target document, which goes from 2 pages to 3:

Page 1 of the source document now sits in front of the target document, which goes from 2 pages to 3


Copy a Range of Pages to the End of a Document

Spire.PDF for JavaScript also provides the PdfDocument.InsertPageRange method, which copies a run of consecutive pages from the source document. It takes the source document plus a start and an end index, has no parameter for a target position, and always appends the result to the end of the current document.

function App() {
  const appendPageRange = async () => {
    // Get the Spire.PDF WASM module
    const pdfModule = window.wasmModule?.spirepdf;

    // Check whether the module is ready
    if (!pdfModule) {
      alert('Spire.PDF is not ready yet');
      return;
    }

    // Load both the source and the target document into the VFS
    const sourceFileName = 'SourceDocument.pdf';
    const targetFileName = 'TargetDocument.pdf';
    await window.spire.FetchFileToVFS(sourceFileName, "", `${process.env.PUBLIC_URL}/data/`);
    await window.spire.FetchFileToVFS(targetFileName, "", `${process.env.PUBLIC_URL}/data/`);

    // Load the two documents
    const sourceDoc = new pdfModule.PdfDocument();
    sourceDoc.LoadFromFile(sourceFileName);
    const targetDoc = new pdfModule.PdfDocument();
    targetDoc.LoadFromFile(targetFileName);

    // Append pages 2 to 3 of the source document to the end of the target document
    // Note: these are positional arguments, not an object; endIndex is inclusive
    targetDoc.InsertPageRange(sourceDoc, 1, 2);

    // Save the result document
    const outputFileName = 'CopyPageRange.pdf';
    targetDoc.SaveToFile(outputFileName);
    sourceDoc.Close();
    targetDoc.Close();

    // Read the generated file from the VFS and trigger the download
    const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([fileArray], { type: 'application/pdf' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = outputFileName;
    a.click();
    URL.revokeObjectURL(url);
  };

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>Copy Page Range</h1>
      <button onClick={appendPageRange}>
        Copy pages 2-3
      </button>
    </div>
  );
}

export default App;

After pages 2 and 3 of the source document are appended, the document has 4 pages:

After pages 2 and 3 of the source document are appended, the document has 4 pages


Copy Every Page of a Whole Document

When an entire document has to move, there is no need to work out the indices first: PdfDocument.AppendPage takes the source document object and appends all of its pages to the end of the current document in their original order. For merging several documents or attaching material to a report, just pass the documents in one after another.

function App() {
  const appendWholeDocument = async () => {
    // Get the Spire.PDF WASM module
    const pdfModule = window.wasmModule?.spirepdf;

    // Check whether the module is ready
    if (!pdfModule) {
      alert('Spire.PDF is not ready yet');
      return;
    }

    // Load both the source and the target document into the VFS
    const sourceFileName = 'SourceDocument.pdf';
    const targetFileName = 'TargetDocument.pdf';
    await window.spire.FetchFileToVFS(sourceFileName, "", `${process.env.PUBLIC_URL}/data/`);
    await window.spire.FetchFileToVFS(targetFileName, "", `${process.env.PUBLIC_URL}/data/`);

    // Load the two documents
    const sourceDoc = new pdfModule.PdfDocument();
    sourceDoc.LoadFromFile(sourceFileName);
    const targetDoc = new pdfModule.PdfDocument();
    targetDoc.LoadFromFile(targetFileName);

    // Use AppendPage when the whole document has to be copied; all pages are appended in order
    targetDoc.AppendPage({ doc: sourceDoc });

    // Save the result document
    const outputFileName = 'CopyAllPages.pdf';
    targetDoc.SaveToFile(outputFileName);
    sourceDoc.Close();
    targetDoc.Close();

    // Read the generated file from the VFS and trigger the download
    const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([fileArray], { type: 'application/pdf' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = outputFileName;
    a.click();
    URL.revokeObjectURL(url);
  };

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>Copy Whole Document</h1>
      <button onClick={appendWholeDocument}>
        Start
      </button>
    </div>
  );
}

export default App;

After all 4 pages of the source document are appended, the document has 6 pages:

After all 4 pages of the source document are appended, the document has 6 pages


Copy Page Content with a Page Template

Spire.PDF for JavaScript also provides the PdfPageBase.CreateTemplate method, which takes the content of one page as a PdfTemplate that Canvas.DrawTemplate then draws onto a newly created page. The first three methods move whole pages, so the new page inherits the source page's size; a template takes the content instead, leaving the page size, the drawing position and the number of times you draw it entirely up to you — the same template can be reused again and again.

function App() {
  const copyPageWithTemplate = async () => {
    // Get the Spire.PDF WASM module
    const pdfModule = window.wasmModule?.spirepdf;

    // Check whether the module is ready
    if (!pdfModule) {
      alert('Spire.PDF is not ready yet');
      return;
    }

    // Load the PDF file to work on into the VFS
    const inputFileName = 'SourceDocument.pdf';
    await window.spire.FetchFileToVFS(inputFileName, "", `${process.env.PUBLIC_URL}/data/`);

    // Load the document
    const doc = new pdfModule.PdfDocument();
    doc.LoadFromFile(inputFileName);

    // Take the page to be reused and turn it into a template: read the content once, draw it many times
    const sourcePage = doc.Pages.get_Item(0);
    const template = sourcePage.CreateTemplate();

    // First placement: insert an A4 page at position 2, a different size from the source,
    // and draw the content scaled to 297.6 x 421.6 at (80, 80)
    const page1 = doc.Pages.Insert(1, new pdfModule.SizeF(595.0, 842.0), new pdfModule.PdfMargins({ margin: 0.0 }));
    page1.Canvas.DrawTemplate(template, new pdfModule.PointF(80.0, 80.0), new pdfModule.SizeF(297.6, 421.6));

    // Second placement: insert another A4 page, drawing the same template smaller in the lower right
    const page2 = doc.Pages.Insert(2, new pdfModule.SizeF(595.0, 842.0), new pdfModule.PdfMargins({ margin: 0.0 }));
    page2.Canvas.DrawTemplate(template, new pdfModule.PointF(320.0, 460.0), new pdfModule.SizeF(200.0, 283.3));

    // Save the result document
    const outputFileName = 'CopyPageWithTemplate.pdf';
    doc.SaveToFile(outputFileName);
    doc.Close();

    // Read the generated file from the VFS and trigger the download
    const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([fileArray], { type: 'application/pdf' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = outputFileName;
    a.click();
    URL.revokeObjectURL(url);
  };

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>Copy Page with Template</h1>
      <button onClick={copyPageWithTemplate}>
        Start
      </button>
    </div>
  );
}

export default App;

When the third argument of DrawTemplate is omitted, the template is drawn at its original coordinates without scaling — the new page is larger than the source, so the content occupies only one corner of it. Both the size of the new page and its margins come from Pages.Insert; in the example the margins are 0 on all four sides, which is what makes the drawing origin the top-left corner of the page.

The content of page 1 in the source document is scaled onto two new A4 pages, taking the document from 4 pages to 6:

The content of page 1 in the source document is scaled onto two new A4 pages, taking the document from 4 pages to 6


Frequently Asked Questions

Creating a page with new PdfMargins(0.0) throws Arg_NullReferenceException

Cause: The PdfMargins constructor treats a single numeric argument as an internal handle, so new pdfModule.PdfMargins(0.0) does not give you a margins object — reading its Left or Top throws Arg_NullReferenceException, and using it to create a page does not produce the margins you expect.

Solution: Pass the margins as an object; for 0 on all four sides write { margin: 0.0 }:

// Zero margins on all four sides
const margins = new pdfModule.PdfMargins({ margin: 0.0 });

// Or set each side separately
const custom = new pdfModule.PdfMargins({ left: 20.0, top: 20.0, right: 20.0, bottom: 20.0 });

An out-of-range or reversed-range error is thrown when copying pages

Cause: Page indices start at 0 and endIndex is inclusive, so the valid range is 0 to Pages.Count - 1. Going outside it throws Index out of range, and a startIndex greater than endIndex throws The start index is greater then the end index.

Solution: Clamp the upper bound with Pages.Count before passing it in:

// To copy pages 2 to 4: start = 1, end = 3, with the page count as the upper bound
const start = 1;
const end = Math.min(3, sourceDoc.Pages.Count - 1);
targetDoc.InsertPageRange(sourceDoc, start, end);

A rotated page comes out with the wrong orientation after copying

Cause: CreateTemplate() takes the page content, and the page's rotation angle (/Rotate) is not part of the template. When the source page is rotated, the template's coordinate system no longer lines up with the target page — drawing it straight onto a new page with DrawTemplate puts the content outside the visible area, and the copy's Rotation is 0.

Solution: When the source page is rotated, use a whole-page copy instead; the content and the rotation angle travel together:

// Whole-page copy: the rotation angle comes with the page
targetDoc.InsertPage({ ldDoc: sourceDoc, pageIndex: 0, resultPageIndex: 1 });

If the template approach is required, temporarily zero the source page's rotation before taking the template, then restore the angle on both the source page and the copy:

const rotation = sourcePage.Rotation.value;

// Zero it temporarily so the template exports at the page's real coordinates
sourcePage.Rotation = 0;
const newPage = doc.Pages.Insert(1, sourcePage.Size, new pdfModule.PdfMargins({ margin: 0.0 }));
newPage.Canvas.DrawTemplate(sourcePage.CreateTemplate(), new pdfModule.PointF(0.0, 0.0));

// Restore the source page and give the copy the same angle
sourcePage.Rotation = rotation;
newPage.Rotation = rotation;

Get a Free License

If you would like to remove the evaluation message from the result documents or lift the feature limits, contact sales for a temporary 30-day license.