Client-side

MERGE PDF

Combine multiple PDF documents into a single PDF file instantly. Reorder files before merging.

📂
Drag & drop PDF files here, or click to browse
Supports multiple .pdf files

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.

Developer Reference

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');
      }
    });