Merge PDFs in the browser with pdf-lib, and what it silently drops
You can merge PDFs entirely in the browser with pdf-lib. No server, no upload, about ten lines. We use exactly this in a small tool, so here is the whole thing, plus the two things it silently drops, which I checked rath
You can merge PDFs entirely in the browser with pdf-lib. No server, no upload, about ten lines. We use exactly this in a small tool, so here is the whole thing, plus the two things it silently drops, which I checked rather than assumed.
The merge
const merged = await PDFLib.PDFDocument.create();
for (const file of files) { // File objects from <input type="file" multiple>
const buf = await file.arrayBuffer();
const src = await PDFLib.PDFDocument.load(buf);
const pages = await merged.copyPages(src, src.getPageIndices());
pages.forEach(p => merged.addPage(p));
}
const bytes = await merged.save();
const url = URL.createObjectURL(new Blob([bytes], { type: "application/pdf" }));
// <a href={url} download="merged.pdf">
Order of the files array is the order of the output. If you let people reorder, reorder that array.
Two practical notes:
-
Copy the file list to an array before your first
await.Array.from(input.files)(or[...dataTransfer.files]in a drop handler) first, then loop: the list from a drop can be empty by the time an awaited call returns. -
Wrap the loop in try/catch.
PDFDocument.loadthrows on files that are not valid PDFs and on password-protected ones. A readable message ("make sure these are valid, non-password-protected PDFs") beats a silent failure.
What copyPages does not carry over
copyPages copies pages, not the document around them. I built two small test files with pdf-lib, one with a bookmark (an outline entry) and a text form field, and merged them with the code above. Result from reading the merged file back:
| Source | Merged | |
|---|---|---|
| Pages | 2 + 3 | 5 |
| Outline (bookmarks) | present | absent |
| Form fields | 1 | 0 |
So for a quick "stick these scans together" tool this is fine, but if your users merge fillable forms or long reports with a table of contents, say so on the page and tell them to check the result before deleting the originals. Rebuilding an outline by hand is possible with pdf-lib's low-level API, but it is not something copyPages does for you.
I only tested bookmarks and text fields. I did not test attachments, tagged-PDF structure or page labels, so check those yourself if you depend on them.
Why client-side is worth it
The usual objection is "browsers are slow". I have not benchmarked it, but copyPages moves PDF objects rather than re-rendering or re-encoding pages, so image quality is untouched. The limit that matters is the device's memory: the merged document grows with every file you add, so very large batches of scans are where a phone will struggle.
The upside is privacy you can prove. Open the Network tab, run a merge, and you should see no request carrying your file. That check is easy to do on any tool, including ours.
If you want to see this running, the tool built on the code above is here: Merge PDF. It is free for up to 5 files and 20 MB; a paid tier lifts that limit.
Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes β full credit and traffic to the original publisher.