How to Merge PDF Files into a Single PDF Using Node.js

IronPDF lets you merge multiple PDF files into a single document in Node.js with just a few lines of code. Load each file with PdfDocument.fromFile(), pass the array to PdfDocument.merge(), and save the result with toFile().

Quickstart: Merge PDF Files

//:path=/static-assets/pdf/content-code-examples/how-to/nodejs-merge-pdf/quickstart-merge.js
// Install: npm install ironpdf
const IronPdf = require('ironpdf');

async function quickMerge() {
  const docs = await Promise.all([
    IronPdf.PdfDocument.fromFile('doc1.pdf'),
    IronPdf.PdfDocument.fromFile('doc2.pdf'),
    IronPdf.PdfDocument.fromFile('doc3.pdf'),
  ]);
  const merged = await IronPdf.PdfDocument.merge(docs);
  await merged.toFile('merged-output.pdf');
}

quickMerge();
//:path=/static-assets/pdf/content-code-examples/how-to/nodejs-merge-pdf/quickstart-merge.js
// Install: npm install ironpdf
const IronPdf = require('ironpdf');

async function quickMerge() {
  const docs = await Promise.all([
    IronPdf.PdfDocument.fromFile('doc1.pdf'),
    IronPdf.PdfDocument.fromFile('doc2.pdf'),
    IronPdf.PdfDocument.fromFile('doc3.pdf'),
  ]);
  const merged = await IronPdf.PdfDocument.merge(docs);
  await merged.toFile('merged-output.pdf');
}

quickMerge();
JAVASCRIPT

IronPDF is a PDF manipulation library for Node.js that handles HTML to PDF conversion, document assembly, and PDF modification without requiring any system-level PDF tools. When merging documents, it preserves fonts, images, embedded forms, and page geometry from each source file.

Before deploying to production, configure your IronPDF license key to remove the trial watermark and enable full output. To set up the rendering engine for your operating system, follow the IronPdfEngine configuration guide.


How Do I Merge Multiple PDF Files in Node.js?

PdfDocument.merge() accepts an array of PdfDocument objects and returns a new document containing every page in the order the source documents appear in the array. The operation is non-destructive; the original file objects are not modified.

//:path=/static-assets/pdf/content-code-examples/how-to/nodejs-merge-pdf/merge-pdfs.js
const IronPdf = require('ironpdf');

async function mergePdfs(outputFilePath, inputFiles) {
  // Load all source documents in parallel
  const pdfDocs = await Promise.all(
    inputFiles.map(file => IronPdf.PdfDocument.fromFile(file))
  );

  // Combine into one document, preserving page order
  const mergedPdf = await IronPdf.PdfDocument.merge(pdfDocs);

  // Write the result to disk
  await mergedPdf.toFile(outputFilePath);
  console.log(`Merged PDF saved to ${outputFilePath}`);
}

(async () => {
  const inputFiles = ['report-jan.pdf', 'report-feb.pdf', 'report-mar.pdf'];
  await mergePdfs('quarterly-report.pdf', inputFiles);
})();
//:path=/static-assets/pdf/content-code-examples/how-to/nodejs-merge-pdf/merge-pdfs.js
const IronPdf = require('ironpdf');

async function mergePdfs(outputFilePath, inputFiles) {
  // Load all source documents in parallel
  const pdfDocs = await Promise.all(
    inputFiles.map(file => IronPdf.PdfDocument.fromFile(file))
  );

  // Combine into one document, preserving page order
  const mergedPdf = await IronPdf.PdfDocument.merge(pdfDocs);

  // Write the result to disk
  await mergedPdf.toFile(outputFilePath);
  console.log(`Merged PDF saved to ${outputFilePath}`);
}

(async () => {
  const inputFiles = ['report-jan.pdf', 'report-feb.pdf', 'report-mar.pdf'];
  await mergePdfs('quarterly-report.pdf', inputFiles);
})();
JAVASCRIPT

Promise.all() loads the source files in parallel rather than one at a time, which matters when processing larger collections. The merge() call concatenates the documents in array order, so place the files in the array in the sequence you want them to appear in the output.

TipsPass file paths as absolute paths or paths relative to the Node.js process working directory. The fromFile() method does not resolve paths relative to the script file itself.

What Does Each Part of the Code Do?

  • PdfDocument.fromFile(): Reads a PDF from disk and returns a PdfDocument object. For full method signatures, see the IronPDF for Node.js API Reference.
  • Promise.all(): Dispatches all file-load operations simultaneously, reducing total load time for multi-document merges. This pattern suits multi-threaded and concurrent PDF generation scenarios as well.
  • PdfDocument.merge(): Concatenates the array of loaded documents into a single PdfDocument, preserving all formatting, images, and embedded content from each source.
  • toFile(): Writes the merged document to the specified path. Combine this with PDF compression to reduce output file size when needed.

How Do I Merge Specific Pages from Each PDF?

Passing every page from every source document is not always the goal. To merge selective page ranges from each input file, extract the desired pages before passing the documents to merge().

//:path=/static-assets/pdf/content-code-examples/how-to/nodejs-merge-pdf/merge-specific-pages.js
const IronPdf = require('ironpdf');

// Each entry specifies a file and the range of pages to include (zero-indexed)
const pageRanges = [
  { file: 'contract.pdf',  startPage: 0, endPage: 2 },
  { file: 'appendix.pdf',  startPage: 0, endPage: 0 },
  { file: 'signature.pdf', startPage: 0, endPage: 0 },
];

async function mergeSpecificPages(outputFile, ranges) {
  const pdfsToMerge = [];

  for (const range of ranges) {
    const pdf = await IronPdf.PdfDocument.fromFile(range.file);
    // extractPages returns a new PdfDocument with only the specified page range
    const pages = pdf.extractPages(range.startPage, range.endPage);
    pdfsToMerge.push(pages);
  }

  const merged = await IronPdf.PdfDocument.merge(pdfsToMerge);
  await merged.toFile(outputFile);
}

mergeSpecificPages('assembled-contract.pdf', pageRanges);
//:path=/static-assets/pdf/content-code-examples/how-to/nodejs-merge-pdf/merge-specific-pages.js
const IronPdf = require('ironpdf');

// Each entry specifies a file and the range of pages to include (zero-indexed)
const pageRanges = [
  { file: 'contract.pdf',  startPage: 0, endPage: 2 },
  { file: 'appendix.pdf',  startPage: 0, endPage: 0 },
  { file: 'signature.pdf', startPage: 0, endPage: 0 },
];

async function mergeSpecificPages(outputFile, ranges) {
  const pdfsToMerge = [];

  for (const range of ranges) {
    const pdf = await IronPdf.PdfDocument.fromFile(range.file);
    // extractPages returns a new PdfDocument with only the specified page range
    const pages = pdf.extractPages(range.startPage, range.endPage);
    pdfsToMerge.push(pages);
  }

  const merged = await IronPdf.PdfDocument.merge(pdfsToMerge);
  await merged.toFile(outputFile);
}

mergeSpecificPages('assembled-contract.pdf', pageRanges);
JAVASCRIPT

extractPages(startPage, endPage) accepts zero-based page indices. Passing 0, 0 extracts only the first page, while 0, 2 extracts the first three pages. The loop builds an array of page-range documents in the order they appear in ranges, then merge() concatenates them into the final output.

This pattern is useful when assembling contracts from a signature page, an appendix, and a cover sheet stored in separate files. You can collect exactly the pages that matter from each source without duplicating files on disk.

Please notePage indices in IronPDF are zero-based. Page 1 of a document is index 0, page 2 is index 1, and so on.


How Do I Add Headers and Footers to a Merged PDF?

After merging, call addHtmlHeader() and addHtmlFooter() on the resulting document to apply consistent headers and footers across all pages. These methods accept an HTML string and an options object.

//:path=/static-assets/pdf/content-code-examples/how-to/nodejs-merge-pdf/merge-with-headers-footers.js
const IronPdf = require('ironpdf');

async function mergeWithHeadersFooters(inputFiles, outputFile) {
  const docs = await Promise.all(
    inputFiles.map(f => IronPdf.PdfDocument.fromFile(f))
  );
  const mergedPdf = await IronPdf.PdfDocument.merge(docs);

  // Apply a styled header to every page
  await mergedPdf.addHtmlHeader('<h3 style="color:#333;">Quarterly Report</h3>', {
    height: 25,
    drawDividerLine: true,
  });

  // Apply a page-numbering footer
  await mergedPdf.addHtmlFooter('<p style="font-size:10px;">Page {page} of {total-pages}</p>', {
    height: 20,
    drawDividerLine: true,
  });

  await mergedPdf.toFile(outputFile);
}

mergeWithHeadersFooters(['q1.pdf', 'q2.pdf', 'q3.pdf'], 'annual-report.pdf');
//:path=/static-assets/pdf/content-code-examples/how-to/nodejs-merge-pdf/merge-with-headers-footers.js
const IronPdf = require('ironpdf');

async function mergeWithHeadersFooters(inputFiles, outputFile) {
  const docs = await Promise.all(
    inputFiles.map(f => IronPdf.PdfDocument.fromFile(f))
  );
  const mergedPdf = await IronPdf.PdfDocument.merge(docs);

  // Apply a styled header to every page
  await mergedPdf.addHtmlHeader('<h3 style="color:#333;">Quarterly Report</h3>', {
    height: 25,
    drawDividerLine: true,
  });

  // Apply a page-numbering footer
  await mergedPdf.addHtmlFooter('<p style="font-size:10px;">Page {page} of {total-pages}</p>', {
    height: 20,
    drawDividerLine: true,
  });

  await mergedPdf.toFile(outputFile);
}

mergeWithHeadersFooters(['q1.pdf', 'q2.pdf', 'q3.pdf'], 'annual-report.pdf');
JAVASCRIPT

The {page} and {total-pages} placeholders are resolved at render time against the merged document's page count. Use drawDividerLine: true to visually separate the header or footer from the page content.

Applying headers and footers after the merge means every page in the combined document gets the same treatment, regardless of which source file it came from. For full header and footer configuration options, see the HTML headers and footers examples.


How Do I Protect a Merged PDF with a Password?

Apply password protection and permission restrictions after merging by calling saveAs() with a security options object. This prevents unauthorized access to the combined document.

//:path=/static-assets/pdf/content-code-examples/how-to/nodejs-merge-pdf/merge-with-security.js
const IronPdf = require('ironpdf');

async function mergeWithSecurity(inputFiles, outputFile) {
  const docs = await Promise.all(
    inputFiles.map(f => IronPdf.PdfDocument.fromFile(f))
  );
  const mergedPdf = await IronPdf.PdfDocument.merge(docs);

  // Restrict the merged document to print-only access
  await mergedPdf.saveAs(outputFile, {
    userPassword: 'viewerpass',
    ownerPassword: 'adminpass',
    allowUserAnnotations: false,
    allowUserCopyPasteContent: false,
    allowUserFormData: false,
    allowUserPrinting: true,
  });
}

mergeWithSecurity(['invoice-1.pdf', 'invoice-2.pdf'], 'secured-invoices.pdf');
//:path=/static-assets/pdf/content-code-examples/how-to/nodejs-merge-pdf/merge-with-security.js
const IronPdf = require('ironpdf');

async function mergeWithSecurity(inputFiles, outputFile) {
  const docs = await Promise.all(
    inputFiles.map(f => IronPdf.PdfDocument.fromFile(f))
  );
  const mergedPdf = await IronPdf.PdfDocument.merge(docs);

  // Restrict the merged document to print-only access
  await mergedPdf.saveAs(outputFile, {
    userPassword: 'viewerpass',
    ownerPassword: 'adminpass',
    allowUserAnnotations: false,
    allowUserCopyPasteContent: false,
    allowUserFormData: false,
    allowUserPrinting: true,
  });
}

mergeWithSecurity(['invoice-1.pdf', 'invoice-2.pdf'], 'secured-invoices.pdf');
JAVASCRIPT

The userPassword is required to open the document; the ownerPassword controls the permission settings themselves. Setting allowUserPrinting: true while disabling other permission flags lets recipients print the document but prevents editing, copying, and annotation. For a full list of available permission flags, refer to the IronPDF Node.js API Reference.

ImportantIf the source PDFs are already password-protected, supply each document's decryption password when loading with fromFile(). Attempting to merge an encrypted document without providing its password will result in an error.


How Do I Merge PDFs and Add a Digital Signature?

Merging PDFs and then immediately signing the result produces a single signed document that covers all source content. Apply applySignature() after merge() to attach a digital certificate to the combined output.

//:path=/static-assets/pdf/content-code-examples/how-to/nodejs-merge-pdf/merge-with-signature.js
const IronPdf = require('ironpdf');

async function mergeAndSign(inputFiles, outputFile, pfxPath, pfxPassword) {
  const docs = await Promise.all(
    inputFiles.map(f => IronPdf.PdfDocument.fromFile(f))
  );
  const mergedPdf = await IronPdf.PdfDocument.merge(docs);

  // Attach a digital signature using a PFX certificate
  await mergedPdf.applySignature(pfxPath, pfxPassword);
  await mergedPdf.toFile(outputFile);
}

mergeAndSign(
  ['section-a.pdf', 'section-b.pdf'],
  'signed-report.pdf',
  'certificate.pfx',
  'certpass'
);
//:path=/static-assets/pdf/content-code-examples/how-to/nodejs-merge-pdf/merge-with-signature.js
const IronPdf = require('ironpdf');

async function mergeAndSign(inputFiles, outputFile, pfxPath, pfxPassword) {
  const docs = await Promise.all(
    inputFiles.map(f => IronPdf.PdfDocument.fromFile(f))
  );
  const mergedPdf = await IronPdf.PdfDocument.merge(docs);

  // Attach a digital signature using a PFX certificate
  await mergedPdf.applySignature(pfxPath, pfxPassword);
  await mergedPdf.toFile(outputFile);
}

mergeAndSign(
  ['section-a.pdf', 'section-b.pdf'],
  'signed-report.pdf',
  'certificate.pfx',
  'certpass'
);
JAVASCRIPT

The applySignature() method embeds the certificate into the PDF metadata so that readers can verify document integrity. This workflow is common in contract assembly pipelines where multiple sections are combined and then co-signed before distribution. For a complete walkthrough of certificate-based signing, see the digital signatures examples.

TipsSign the merged document rather than the individual source files when you need a single certificate that attests to the entire combined document.


How Do I Troubleshoot Common PDF Merge Errors?

Most errors during merging fall into four categories: missing files, memory exhaustion, corrupted inputs, and permission issues. The table below lists the most frequent causes and how to address each.

Common PDF merge errors and their solutions
ErrorLikely CauseSolution
File not foundIncorrect path or working directory mismatchUse absolute paths or verify the process working directory
JavaScript heap out of memoryLoading many large PDFs simultaneouslyIncrease Node.js memory: node --max-old-space-size=4096 script.js
Invalid or corrupt PDFSource file is damaged or not a valid PDFValidate source files with a PDF reader before processing
Permission deniedNo read access on input or no write access on output directoryCheck file and directory permissions on the operating system
Encrypted source PDFInput PDF requires a password to openPass the password as the second argument to fromFile()

For environment-specific setup problems, the IronPDF changelog documents known issues and fixes. If problems persist after checking the table above, wrap the merge call in a try-catch block to surface the full error message from the library.

CautionAlways validate that every file path in your input array exists before calling fromFile(). A single missing file causes the entire Promise.all() call to reject, canceling the merge.

Node.js itself provides useful tools for pre-flight path validation. The Node.js fs.promises.access method lets you check whether a file is readable before passing it to IronPDF. For questions about how community developers handle similar merge error scenarios, the Stack Overflow thread on merging PDFs in Node.js provides additional context.


What Are the Next Steps for Merging PDFs in Node.js?

The examples above cover the most common merge scenarios: basic file combination, page-range selection, header and footer injection, password protection, and digital signing. IronPDF also supports managing PDF forms across merged pages, stamping new content onto the combined document, and producing rasterized images from the final output for preview generation.

Start your free 30-day trial to test merging without a watermark, or view licensing options if you are ready to deploy.

Ready to see what else IronPDF can do? Visit the IronPDF for Node.js documentation for the full API walkthrough and additional how-to guides.

Frequently Asked Questions

How do I merge multiple PDF files into one in Node.js?

Load each file with PdfDocument.fromFile(), collect the results in an array, pass the array to PdfDocument.merge(), and save the output with toFile(). Use Promise.all() to load files in parallel when merging three or more documents.

Does IronPDF preserve formatting when merging PDFs?

Yes. IronPDF preserves fonts, images, embedded forms, and page geometry from each source document. The merged output maintains the original layout of every page in the order the source files appear in the input array.

How do I merge only specific pages from each PDF?

Call extractPages(startPage, endPage) on each loaded document before passing it to PdfDocument.merge(). Page indices are zero-based, so the first page is index 0. The returned document contains only the specified range.

Can I add headers and footers to a merged PDF?

Yes. After merging, call addHtmlHeader() and addHtmlFooter() on the resulting document. Both methods accept an HTML string and an options object. Use the {page} and {total-pages} placeholders for automatic page numbering.

How do I password-protect a merged PDF in Node.js?

Call saveAs() with a security options object specifying userPassword, ownerPassword, and permission flags such as allowUserPrinting. The userPassword is required to open the document; the ownerPassword controls the permission settings.

What should I do if merging fails with a file-not-found error?

Verify that all input file paths are correct relative to the Node.js process working directory, or switch to absolute paths. Use fs.promises.access() to confirm each file is readable before calling PdfDocument.fromFile().

What happens if one of the source PDFs is encrypted?

Attempting to load an encrypted PDF without providing its password will throw an error and reject the entire Promise.all() call. Supply the document password as the second argument to fromFile() before merging.

Do I need a license key to merge PDFs with IronPDF?

IronPDF works without a license key during development, but output documents include a trial watermark. Configure a valid license key before deploying to production to remove the watermark and enable full functionality.

Darrius Serrant
Full Stack Software Engineer (WebOps)

Darrius Serrant holds a Bachelor’s degree in Computer Science from the University of Miami and works as a Full Stack WebOps Marketing Engineer at Iron Software. Drawn to coding from a young age, he saw computing as both mysterious and accessible, making it the perfect medium for creativity ...

Read More
Ready to Get Started?
Version: 2026.4 just released
Still Scrolling Icon

Still Scrolling?

Want proof fast?
run a sample watch your HTML become a PDF.