People merge and split PDFs all the time — extract a signature page, combine receipts, pull one diagram out of a 200-page report. Every online tool I found either uploads your files to a server (fine for public docs, bad for NDAs) or locks basic features behind a subscription.
So I built a tool that runs entirely in the browser. No backend. No sign-up. Your PDF never leaves your machine.
Here's how it works, and what it took to build.
The constraint that shaped everything: no backend
The entire app is static files: one HTML page, some CSS, and JavaScript loaded from CDNs. All PDF manipulation happens client-side using pdf-lib, a WebAssembly-compiled library that loads directly in the browser.
That constraint buys three things for free:
- Privacy — your documents never touch a server. Not even logs.
- Offline — works with WiFi off; useful on trains, planes, secure facilities.
- Zero hosting cost — it's static HTML, so nginx serves it for free.
Merging PDFs
The core merge logic is about eight lines of code:
const merged = await PDFDocument.create();
for (const file of files) {
const doc = await PDFDocument.load(await file.arrayBuffer());
const pages = await merged.copyPages(doc, doc.getPageIndices());
pages.forEach(p => merged.addPage(p));
}
const bytes = await merged.save();
downloadBlob(new Blob([bytes], { type: 'application/pdf' }), 'merged.pdf');
file.arrayBuffer() reads the uploaded file directly from the user's filesystem — no fetch, no XMLHttpRequest. Then copyPages + addPage stitches them together. pdf-lib handles the PDF structure, cross-references, and object streams under the hood.
Splitting PDFs
Splitting is the inverse: load one PDF, let users pick page ranges, write out new PDFs with just those pages:
const doc = await PDFDocument.load(fileBytes);
const rangeText = document.getElementById('split-range').value;
const pages = parseRanges(rangeText, doc.getPageCount());
// Group consecutive pages into separate downloads
const groups = [];
let cur = null;
for (const p of pages) {
if (!cur || p !== cur.end + 1) {
cur = { start: p, end: p };
groups.push(cur);
} else {
cur.end = p;
}
}
for (const g of groups) {
const out = await PDFDocument.create();
const indices = Array.from(
{ length: g.end - g.start + 1 },
(_, i) => g.start - 1 + i
);
const srcPages = await out.copyPages(doc, indices);
srcPages.forEach(p => out.addPage(p));
const bytes = await out.save();
downloadBlob(new Blob([bytes]), `pages-${g.start}-${g.end}.pdf`);
}
The tricky bits
-
Password-protected PDFs — pdf-lib can read encrypted PDFs if you pass
userPassword, but stripping passwords requires knowing the password first. This is the #1 Pro feature request. - Large PDFs in memory — the browser has limits. A 50MB PDF can spike memory usage to 200MB+ during operations. Acceptable for most use cases, but worth documenting.
- PDF.js for thumbnails — rendering page previews requires the much larger PDF.js library (~700KB gzipped). For v1, we show numbered blocks instead to keep load times snappy.
Design decisions
Browser over Electron
Most "desktop" PDF tools are really Electron apps wrapped around the same web tech. For privacy-focused users, a browser-based tool has better guarantees: no OS-level access, no sandbox escapes, no auto-updater you didn't approve. The user gets the same functionality with stronger privacy boundaries.
$19 one-time, not subscription
Smallpdf charges ~$15/month (~$180/year). We charge $19 once. The math is simple: if you use this more than two weeks, you've already beaten the subscription. And since it runs in your browser, "updates" are just new HTML/CSS/JS — free forever after purchase.
CDN-loaded libraries
pdf-lib loads from cdn.jsdelivr.net. That's one HTTP request, ~300KB gzipped. Caches well, no server processing needed. Trade-off: requires internet on first load. We accept that — the alternative is shipping a 10MB native app.
Open source spirit
The free tier does everything — merge unlimited PDFs, split by page range, download instantly. The Pro tier ($19 one-time) adds advanced splitting: single-page extraction, rotation within splits, password-protected PDF support, batch rename. But the core tool is genuinely usable without paying.
Try it here: https://pdfmerge.jackgreen.top/?utm_source=devto&utm_medium=article&utm_campaign=pdfmerge-launch
If you're building something similar, pdf-lib's docs are excellent. The copyPages API is the workhorse — everything from merge to split to rotation boils down to copying pages between documents.
Top comments (0)