MERGE PDF
Combine multiple PDF documents into a single PDF file instantly. Reorder files before merging.
About Merge PDF Tool
Merge PDF is a free online tool that lets you combine multiple PDF documents into a single file quickly and securely. There are no limits on the number of files you can combine, and the entire process is handled locally inside your web browser. This means your private documents are never uploaded to any servers.
With support for drag-and-drop file organization, you can easily rearrange the order of your files before merging. The tool preserves the original quality, formatting, page orientation, and structure of each individual PDF page, combining them seamlessly in seconds.
Core Algorithm & Standalone Script
Standalone, zero-dependency JavaScript implementation powering this tool. Free to inspect, copy, and build upon.
let selectedFiles = [];
const uploadZone = document.getElementById('uploadZone');
const fileInput = document.getElementById('fileInput');
const fileListEl = document.getElementById('fileList');
const actionContainer = document.getElementById('actionContainer');
const mergeBtn = document.getElementById('mergeBtn');
const loadingContainer = document.getElementById('loadingContainer');
const resultContainer = document.getElementById('resultContainer');
const resultInfo = document.getElementById('resultInfo');
const downloadBtn = document.getElementById('downloadBtn');
// Drag and drop event listeners
['dragenter', 'dragover'].forEach(eventName => {
uploadZone.addEventListener(eventName, e => {
e.preventDefault();
uploadZone.classList.add('dragover');
}, false);
});
['dragleave', 'drop'].forEach(eventName => {
uploadZone.addEventListener(eventName, e => {
e.preventDefault();
uploadZone.classList.remove('dragover');
}, false);
});
uploadZone.addEventListener('drop', e => {
const dt = e.dataTransfer;
const files = dt.files;
handleFiles(files);
});
fileInput.addEventListener('change', e => {
handleFiles(e.target.files);
});
function handleFiles(files) {
for (let i = 0; i < files.length; i++) {
if (files[i].type === 'application/pdf') {
selectedFiles.push({
id: Date.now() + '-' + Math.random().toString(36).substr(2, 9),
file: files[i]
});
}
}
renderFileList();
}
function formatBytes(bytes, decimals = 2) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const dm = decimals < 0 ? 0 : decimals;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
}
function renderFileList() {
fileListEl.innerHTML = '';
selectedFiles.forEach((item, index) => {
const file = item.file;
const fileItem = document.createElement('div');
fileItem.className = 'file-item';
const fileInfo = document.createElement('div');
fileInfo.className = 'file-info';
fileInfo.innerHTML = `
<span style="font-size: 16px;">📄</span>
<div>
<div class="file-name" title="${file.name}">${file.name}</div>
<div class="file-size">${formatBytes(file.size)}</div>
</div>
`;
const fileControls = document.createElement('div');
fileControls.className = 'file-controls';
// Move Up button
if (index > 0) {
const upBtn = document.createElement('button');
upBtn.className = 'ctrl-btn';
upBtn.textContent = '▲';
upBtn.onclick = () => moveFile(index, -1);
fileControls.appendChild(upBtn);
}
// Move Down button
if (index < selectedFiles.length - 1) {
const downBtn = document.createElement('button');
downBtn.className = 'ctrl-btn';
downBtn.textContent = '▼';
downBtn.onclick = () => moveFile(index, 1);
fileControls.appendChild(downBtn);
}
// Remove button
const removeBtn = document.createElement('button');
removeBtn.className = 'ctrl-btn remove-btn';
removeBtn.textContent = '✕';
removeBtn.onclick = () => removeFile(item.id);
fileControls.appendChild(removeBtn);
fileItem.appendChild(fileInfo);
fileItem.appendChild(fileControls);
fileListEl.appendChild(fileItem);
});
if (selectedFiles.length >= 2) {
actionContainer.classList.remove('hidden');
} else {
actionContainer.classList.add('hidden');
}
resultContainer.classList.add('hidden');
}
function moveFile(index, direction) {
const temp = selectedFiles[index];
selectedFiles[index] = selectedFiles[index + direction];
selectedFiles[index + direction] = temp;
renderFileList();
}
function removeFile(id) {
selectedFiles = selectedFiles.filter(item => item.id !== id);
renderFileList();
}
mergeBtn.addEventListener('click', async () => {
if (selectedFiles.length < 2) return;
actionContainer.classList.add('hidden');
loadingContainer.classList.remove('hidden');
resultContainer.classList.add('hidden');
try {
const { PDFDocument } = PDFLib;
const mergedPdf = await PDFDocument.create();
for (const item of selectedFiles) {
const bytes = await item.file.arrayBuffer();
const pdf = await PDFDocument.load(bytes);
const copiedPages = await mergedPdf.copyPages(pdf, pdf.getPageIndices());
copiedPages.forEach((page) => mergedPdf.addPage(page));
}
const mergedPdfBytes = await mergedPdf.save();
const blob = new Blob([mergedPdfBytes], { type: 'application/pdf' });
const blobUrl = URL.createObjectURL(blob);
downloadBtn.href = blobUrl;
resultInfo.textContent = `Combined ${selectedFiles.length} files into a single PDF. Total size: ${formatBytes(blob.size)}.`;
loadingContainer.classList.add('hidden');
resultContainer.classList.remove('hidden');
} catch (err) {
console.error(err);
alert('An error occurred while merging your PDF files. Please ensure none of the files are corrupted or password-protected.');
loadingContainer.classList.add('hidden');
actionContainer.classList.remove('hidden');
}
});