JavaScript Client-Side PDF Merging using pdf-lib in Browser

JavaScript Client-Side PDF Merging using pdf-lib in Browser

Whether you are building workflow automation utilities or document management modules for a web app, merging multiple PDF files into a single master document is a common requirement. Traditionally, this meant uploading heavy binary files to a backend server (running Node.js, Python, or Java), executing a library to merge them, and sending the compiled file back to the browser.

However, server-side merging introduces latency, consumes server RAM and CPU, and poses security risks—especially when handling sensitive documents like business contracts or personal financial files. The solution is client-side PDF merging. By executing the merge logic directly inside the user's browser, you eliminate server overhead and secure the data. In this guide, we will implement client-side PDF merging using the popular library pdf-lib.


1. Comparing Server-Side vs. Browser Client-Side PDF Merging

Client-side PDF merging reads uploaded files as binary arrays (ArrayBuffer) via the HTML5 File API, instantiates them as document objects, and merges pages in browser memory.

Moving the processing load to the client is highly cost-effective. It reduces server infrastructure costs to zero, making it ideal for free web tools or SaaS applications.

Comparison Server-Side PDF Merging Client-Side PDF Merging
Data Privacy & Leak Risk High (files sent over the wire and saved to server disk) Zero (files never leave the user's device)
Server Cost & Resource Use High CPU/RAM load on server during file processing Zero (utilizes client device processor)
Latency & Network wait High (network upload and download times are mandatory) Near-instant (no files are transferred over the network)
File Size Limits Limited only by server specs and timeouts Limited by client device browser memory (RAM)

2. Implementing PDF Merging with pdf-lib

Let's write a module to merge multiple PDF files in-memory using pdf-lib. This script runs on modern ES6 browser environments.

1) Installation

  • Via CDN:
    <script src="https://unpkg.com/pdf-lib/dist/pdf-lib.min.js"></script>
    
  • Via npm:
    npm install pdf-lib
    

2) PDF Merging Script Code

import { PDFDocument } from 'pdf-lib';

/**
 * Merges multiple PDF files in the browser and triggers an automatic download.
 * @param {File[]} pdfFiles - Array of File objects selected from a file input.
 */
export async function mergePDFFiles(pdfFiles) {
  try {
    // 1. Create a new empty master PDF Document
    const mergedPdfDoc = await PDFDocument.create();

    // 2. Loop through each file sequentially to merge pages
    for (const file of pdfFiles) {
      // Read File data as an ArrayBuffer
      const fileArrayBuffer = await file.arrayBuffer();
      
      // Load the PDF document
      const pdfDoc = await PDFDocument.load(fileArrayBuffer);
      
      // Get all page indices of the loaded PDF
      const pageIndices = pdfDoc.getPageIndices();
      
      // Copy pages into the master document
      const copiedPages = await mergedPdfDoc.copyPages(pdfDoc, pageIndices);
      
      // Add copied pages to the master document
      copiedPages.forEach((page) => {
        mergedPdfDoc.addPage(page);
      });
    }

    // 3. Save the master PDF document as bytes (Uint8Array)
    const mergedPdfBytes = await mergedPdfDoc.save();

    // 4. Create a Blob and Object URL for browser download
    const blob = new Blob([mergedPdfBytes], { type: 'application/pdf' });
    const downloadUrl = URL.createObjectURL(blob);
    
    // 5. Trigger download via a temporary anchor element
    const link = document.createElement('a');
    link.href = downloadUrl;
    link.download = `merged_${Date.now()}.pdf`;
    document.body.appendChild(link);
    link.click();
    
    // 6. Cleanup DOM and revoke object URL to free up heap memory
    document.body.removeChild(link);
    URL.revokeObjectURL(downloadUrl);
    
    return true;
  } catch (error) {
    console.error("Error during PDF merging:", error);
    throw error;
  }
}

3. Optimizing Memory for Large PDF Files

Because client-side operations rely on the user's local hardware resources, keep these best practices in mind to prevent browser tab crashes:

  • Avoid Parallel Page Copying: Do not copy pages using parallel mapping like Promise.all(). Merging large files in parallel can spike RAM usage. Using a sequential for...of loop instead keeps memory footprints low.
  • Index Selection: Rather than copying all pages, you can parse the page index array and select specific pages (e.g., only cover pages or page ranges) to pass into copyPages, reducing processing overhead.

4. Frequently Asked Questions (FAQ)

Q1. How do I merge password-protected PDF files?

Attempting to load encrypted PDFs using PDFDocument.load(bytes) will throw an error. You must capture the user's password and pass it into the loading options parameters: await PDFDocument.load(bytes, { ignoreEncryption: false }) or decrypt it using a password string.

Q2. Are digital signatures and form data preserved after merging?

Copying pages using copyPages treats pages as standalone layouts. Any existing digital signatures or interactive form inputs (AcroForm data) may be lost. If you need to preserve form data, you must copy the AcroForm definition fields from the source documents to the master document using pdf-lib form helper APIs.


5. Live Testing and Utilities

If you want to test how your pdf-lib configuration handles large documents, try our free client-side PDF Merger. Your documents are processed locally inside your browser sandbox. To render PDF documents onto high-resolution image canvases afterwards, read our PDF Rendering & Canvas Scaling Guide blog post.

Recommended Articles

Back to List