In this post I’ll show how to merge PDF files entirely in the browser using PDF.js and pdf-lib — no server, no file upload, no backend. Everything runs on the user’s machine, which is great for privacy and for keeping hosting costs at zero (it’s just a static site).
Why process PDFs on the client?
Most “free” PDF websites quietly upload your documents to their server, which:
- Exposes private/sensitive files to third parties
- Imposes size limits
- Often slaps a watermark on the output
- Requires you to trust their storage
If you handle PDFs with client-side JavaScript (WebAssembly / WASM + PDF.js), none of that happens. The user’s file never leaves their device, and you don’t need a backend at all — so it’s cheap and private.
Caveats
- pdf-lib works well with standard PDFs; heavily encrypted or unusual documents may need extra handling.
- Very large PDFs are memory-hungry since everything is client-side, but for typical documents it’s fast and free.
- Some complex PDFs with unusual fonts can lose fidelity — test on your own files first.
Try it
I packaged this approach (plus split, compress, rotate, unlock, image-to-PDF) into a free no-upload tool: https://yourutilityhub.com/pdf/merge-pdf
The whole project is open source: https://github.com/Jalal-khn/utilityhub-
If you have questions about the architecture or want a deeper dive on any part, ask away.
The basic idea
- Read the input file with
FileReader - Parse it with
pdf-lib(a pure-JS PDF library) - Copy the source pages into a new document
- Save the merged PDF and trigger a download
Here’s the core function:
js
import { PDFDocument } from "pdf-lib";
async function mergePdfs(files) {
const merged = await PDFDocument.create();
for (const file of files) {
const bytes = await file.arrayBuffer();
const src = await PDFDocument.load(bytes, { ignoreEncryption: true });
const pages = await merged.copyPages(src, src.getPageIndices());
pages.forEach((page) => merged.addPage(page));
}
const out = await merged.save();
return new Blob([out], { type: "application/pdf" });
}