I needed to merge ten NDA contracts into a single filing document during a flight with intermittent Wi-Fi. Every popular online PDF tool failed โ either the upload timed out or the tool capped free use at five files. I ended up writing a 30-line pdf-lib script in the browser console. It merged all ten in under two seconds and never sent a byte to any server.
Merge PDFs locally โ no upload, no file limit.
Drop your PDFs, reorder them, download the merged file. Your documents stay on your device.
Open PDF Merger โWhy You Should Not Upload PDFs to Merge Tools
When you upload a PDF to an online tool, the file travels to a server. For contracts, legal documents, medical records, or financial statements, that upload is a real risk. You have no way to verify:
- Whether the server deletes the file after processing
- What jurisdiction the server is in (relevant to GDPR compliance)
- Whether the upload is logged in the tool's analytics or audit trail
- Who has access to the server and its stored files
Browser-based merging eliminates all of these concerns. The PDF bytes are read by your browser's JavaScript engine, processed in local RAM, and written back to disk via a download prompt. Nothing leaves the browser tab.
How PDFs Work Internally
A PDF file is not just a flat sequence of pages. It is structured as a hierarchy of objects:
PDF file structure (simplified): โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ Header: %PDF-1.7 โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค โ Body: Objects (pages, fonts, โ โ images, annotations) โ โ 1 0 obj โ Catalog (root) โ โ 2 0 obj โ Pages tree root โ โ 3 0 obj โ Page 1 โ โ 4 0 obj โ Page 2 โ โ 5 0 obj โ Font resource โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค โ Cross-Reference Table (XREF): โ โ Maps object numbers โ byte offsetsโ โ Enables random access to pages โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค โ Trailer: Points to XREF table start โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Merging PDFs means creating a new document, copying all page objects from each source PDF into the new document's object pool, and building a new XREF table that references them all. No image re-encoding, no re-rendering โ just object-level copying and index rebuilding.pdf-libhandles all of this.
Basic Merge with pdf-lib
import { PDFDocument } from 'pdf-lib';
async function mergePDFs(files: File[]): Promise<Blob> {
// Step 1: Create the output document
const mergedDoc = await PDFDocument.create();
for (const file of files) {
// Step 2: Read each file as ArrayBuffer
const arrayBuffer = await file.arrayBuffer();
// Step 3: Load the source PDF
const sourcePdf = await PDFDocument.load(arrayBuffer, {
ignoreEncryption: false, // Reject encrypted PDFs
});
// Step 4: Copy all pages into the merged document
const pageCount = sourcePdf.getPageCount();
const copiedPages = await mergedDoc.copyPages(
sourcePdf,
[...Array(pageCount).keys()] // [0, 1, 2, ..., pageCount-1]
);
// Step 5: Add each copied page in order
copiedPages.forEach(page => mergedDoc.addPage(page));
}
// Step 6: Serialize to Uint8Array
const mergedBytes = await mergedDoc.save();
// Step 7: Return as downloadable Blob
return new Blob([mergedBytes], { type: 'application/pdf' });
}
// Usage
const fileInput = document.getElementById('files') as HTMLInputElement;
fileInput.addEventListener('change', async () => {
const files = Array.from(fileInput.files ?? []);
if (files.length < 2) {
alert('Select at least 2 PDF files');
return;
}
const merged = await mergePDFs(files);
const url = URL.createObjectURL(merged);
const link = document.createElement('a');
link.href = url;
link.download = 'merged.pdf';
link.click();
URL.revokeObjectURL(url);
});Merging Specific Pages, Not Entire Documents
Sometimes you need only certain pages from each source PDF โ not the whole document.copyPages accepts any array of page indices:
// Copy only pages 1, 3, and 5 from a PDF (0-indexed: 0, 2, 4) const selectedPages = await mergedDoc.copyPages(sourcePdf, [0, 2, 4]); selectedPages.forEach(page => mergedDoc.addPage(page)); // Copy pages in reverse order (reverse the whole document) const allIndices = [...Array(sourcePdf.getPageCount()).keys()].reverse(); const reversedPages = await mergedDoc.copyPages(sourcePdf, allIndices); reversedPages.forEach(page => mergedDoc.addPage(page)); // Copy last page only const lastPage = sourcePdf.getPageCount() - 1; const [lastCopy] = await mergedDoc.copyPages(sourcePdf, [lastPage]); mergedDoc.addPage(lastCopy);
Setting Merged Document Metadata
// Set document metadata after merging
mergedDoc.setTitle('Merged Contracts โ Q3 2026');
mergedDoc.setAuthor('Azeem Mustafa');
mergedDoc.setSubject('Contract bundle for review');
mergedDoc.setKeywords(['contracts', 'NDA', '2026']);
mergedDoc.setCreator('FileMint PDF Merger');
mergedDoc.setCreationDate(new Date());
mergedDoc.setModificationDate(new Date());Problems You Will Hit and How to Fix Them
Encrypted / Password-Protected PDFs
pdf-lib throws when trying to load an encrypted PDF:Error: Input document to PDFDocument.load is encrypted.
// Handle encrypted PDFs gracefully
try {
const sourcePdf = await PDFDocument.load(arrayBuffer);
} catch (err) {
if (err instanceof Error && err.message.includes('encrypted')) {
// Show user-friendly error โ they need to unlock the PDF first
console.error('This PDF is password-protected. Remove the password before merging.');
// Options:
// 1. Ask user to re-export without password protection
// 2. Use a PDF unlock tool first (local, privacy-preserving)
}
}Large Files and Memory Limits
Chrome's V8 heap limit means merging very large PDFs can crash the browser tab. For PDFs over 50MB each, process them in series and release memory between files:
// Memory-conscious merge: process one file at a time
// and release the source document reference immediately
for (const file of files) {
const arrayBuffer = await file.arrayBuffer();
let sourcePdf = await PDFDocument.load(arrayBuffer);
const indices = [...Array(sourcePdf.getPageCount()).keys()];
const pages = await mergedDoc.copyPages(sourcePdf, indices);
pages.forEach(p => mergedDoc.addPage(p));
// Explicitly null the reference to help the garbage collector
// (This is best-effort โ V8 GC timing is not guaranteed)
sourcePdf = null as unknown as PDFDocument;
// For very large sets, yield to the event loop between files
await new Promise(resolve => setTimeout(resolve, 0));
}Scanned PDFs Are Just Images
A scanned PDF contains rasterised page images โ not searchable text. pdf-lib can merge scanned PDFs without problems. But if you need to search, copy, or index the content, you need OCR before merging. No client-side JavaScript library provides full OCR โ that requires either Tesseract.js (open-source, slower) or a cloud OCR API.
Form Fields After Merging
pdf-lib copies form fields, but if two source PDFs have fields with identical names, they will conflict in the merged document. Flatten form fields before merging if the forms are already filled:
// Flatten all form fields in a PDF (makes them non-editable but merge-safe) const form = sourcePdf.getForm(); form.flatten(); // Rasterises field values into page content // Now copy pages โ no field name conflicts in the merged output const pages = await mergedDoc.copyPages(sourcePdf, indices);
When to Use Each Approach
| Scenario | Recommended Tool |
|---|---|
| Merging contracts or confidential documents | Browser-based (pdf-lib) โ local, no upload |
| Files totalling over 300MB | Node.js pdf-lib script or server-side tool |
| Non-sensitive documents, occasional use | Any tool โ browser-based preferred for speed |
| Password-protected PDFs | Remove password first with a local tool, then merge |
| Scanned PDFs needing OCR before merge | Tesseract.js (browser) or a server-side OCR pipeline |
If you are working with PDFs that grew after compression (a surprisingly common problem), read our guide on why PDFs get larger after compression โ it covers the font embedding and image re-encoding issues that cause it. For a full overview of browser-based PDF operations, our PDF transformation guide covers splitting, rotating, extracting pages, and compressing โ all locally.
Merge PDFs locally โ no upload, no file limit.
Drop multiple PDFs, drag to reorder, download the merged result. Your documents never leave your device.
Open PDF Merger โ