MERGE PDF

Merge PDF – Free Online PDF Combiner | SnapToolSet
100% Client-Side · Zero Upload

Merge PDF Files
Instantly & Privately

Combine multiple PDF documents into a single file. Reorder pages, then download — your files never leave your browser.

Drop PDF files here

or browse to upload

Supports: PDF  ·  Max 200MB per file

Files to Merge 0
No files added yet. Drop PDFs above.
Output filename
Processing… 0%
🔒
100% Private
All processing happens in your browser. Files are never uploaded to any server.
🔀
Reorder Pages
Move files up or down to control page order in the final merged PDF.
Instant Merge
Merge dozens of PDFs in seconds using the native pdf-lib engine.
📱
Works Everywhere
Fully responsive — works on desktop, tablet, and mobile without any app install.
${escHtml(item.name)}
📄 ${item.pageCount} page${item.pageCount !== 1 ? 's' : ''} 💾 ${formatSize(item.size)} #${idx + 1}
`; /* Card drag-sort events */ card.addEventListener('dragstart', () => { dragSrcId = item.id; card.classList.add('dragging-card'); }); card.addEventListener('dragend', () => { dragSrcId = null; card.classList.remove('dragging-card'); document.querySelectorAll('.drag-target').forEach(el => el.classList.remove('drag-target')); }); card.addEventListener('dragover', e => { e.preventDefault(); card.classList.add('drag-target'); }); card.addEventListener('dragleave', () => card.classList.remove('drag-target')); card.addEventListener('drop', e => { e.preventDefault(); card.classList.remove('drag-target'); if (!dragSrcId || dragSrcId === item.id) return; const srcIdx = files.findIndex(f => f.id === dragSrcId); const destIdx = files.findIndex(f => f.id === item.id); if (srcIdx < 0 || destIdx < 0) return; const [moved] = files.splice(srcIdx, 1); files.splice(destIdx, 0, moved); renderList(); }); fileListEl.appendChild(card); }); /* Button events */ fileListEl.querySelectorAll('[data-action]').forEach(btn => { btn.addEventListener('click', () => { const id = btn.dataset.id; const act = btn.dataset.action; const idx = files.findIndex(f => f.id === id); if (act === 'remove') { files.splice(idx, 1); renderList(); } else if (act === 'up' && idx > 0) { [files[idx - 1], files[idx]] = [files[idx], files[idx - 1]]; renderList(); } else if (act === 'down' && idx < files.length - 1) { [files[idx], files[idx + 1]] = [files[idx + 1], files[idx]]; renderList(); } }); }); } /* Clear all */ document.getElementById('clear-all').addEventListener('click', () => { files.length = 0; renderList(); }); /* ====================================== MERGE ====================================== */ mergeBtn.addEventListener('click', mergePDFs); async function mergePDFs() { if (files.length < 2) { toast('Add at least 2 PDF files to merge.', 'error'); return; } const progressWrap = document.getElementById('progress-wrap'); const progressBar = document.getElementById('progress-bar'); const progressText = document.getElementById('progress-text'); const progressPct = document.getElementById('progress-pct'); mergeBtn.disabled = true; progressWrap.classList.add('active'); setProgress(0, 'Reading files…'); try { const merged = await PDFLib.PDFDocument.create(); for (let i = 0; i < files.length; i++) { const pct = Math.round(((i) / files.length) * 85); setProgress(pct, `Merging ${i + 1} of ${files.length}: ${files[i].name}`); const buf = await files[i].file.arrayBuffer(); const doc = await PDFLib.PDFDocument.load(buf, { ignoreEncryption: true }); const pageIndices = doc.getPageIndices(); const copied = await merged.copyPages(doc, pageIndices); copied.forEach(p => merged.addPage(p)); } setProgress(90, 'Finalising PDF…'); const bytes = await merged.save(); setProgress(100, 'Done!'); const outputName = (document.getElementById('output-name').value.trim() || 'merged-document') + '.pdf'; triggerDownload(bytes, outputName); toast(`✅ Merged ${files.length} files → ${outputName}`, 'success'); setTimeout(() => { progressWrap.classList.remove('active'); mergeBtn.disabled = false; setProgress(0, 'Processing…'); }, 2500); } catch (err) { console.error(err); toast('Merge failed: ' + (err.message || 'Unknown error'), 'error'); progressWrap.classList.remove('active'); mergeBtn.disabled = false; } } function setProgress(pct, label) { document.getElementById('progress-bar').style.width = pct + '%'; document.getElementById('progress-text').textContent = label; document.getElementById('progress-pct').textContent = pct + '%'; } function triggerDownload(bytes, filename) { const blob = new Blob([bytes], { type: 'application/pdf' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = filename; document.body.appendChild(a); a.click(); document.body.removeChild(a); setTimeout(() => URL.revokeObjectURL(url), 5000); } /* ====================================== UTILITIES ====================================== */ function formatSize(bytes) { if (bytes < 1024) return bytes + ' B'; if (bytes < 1048576) return (bytes / 1024).toFixed(1) + ' KB'; return (bytes / 1048576).toFixed(2) + ' MB'; } function escHtml(str) { return str.replace(/&/g,'&').replace(//g,'>').replace(/"/g,'"'); } function toast(msg, type = 'success') { const el = document.createElement('div'); el.className = `toast ${type}`; el.innerHTML = `${type === 'success' ? '✅' : '⚠️'}${escHtml(msg)}`; const container = document.getElementById('toast-container'); container.appendChild(el); setTimeout(() => { el.style.animation = 'toastOut .3s ease forwards'; setTimeout(() => el.remove(), 350); }, 4000); }

This website uses cookies.