DEV Community

Jalal khan
Jalal khan

Posted on

Merge PDFs in the browser with JavaScript (no uploads, no server)

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

  1. Read the input file with FileReader
  2. Parse it with pdf-lib (a pure-JS PDF library)
  3. Copy the source pages into a new document
  4. 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" });
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)