How-to guides/Merge & Split

Merge PDFs and generate a table of contents in Rust

Combine multiple PDF files into one and insert a generated table of contents page with clickable bookmark links.

rust
use pdfluent::{PdfDocument, PdfMerger, BookmarkMergeStrategy};

fn main() -> pdfluent::Result<()> {
    let merged = PdfMerger::new()
        .add(PdfDocument::open("intro.pdf")?)
        .add(PdfDocument::open("body.pdf")?)
        .with_bookmarks(BookmarkMergeStrategy::Concat)
        .with_page_labels(true)
        .build()?;
    merged.save("merged.pdf")?;
    Ok(())
}

Step by step

1

Prepare MergeInput entries

Each MergeInput specifies a source file and an optional title used for the TOC entry and bookmark.

rust
use pdfluent::{PdfDocument, PdfMerger, BookmarkMergeStrategy};

let merger = PdfMerger::new()
    .add(PdfDocument::open("section1.pdf")?)
    .add(PdfDocument::open("section2.pdf")?)
    .add(PdfDocument::open("appendix.pdf")?);
2

Configure merge options

Enable TOC generation and optionally configure the TOC page style, font, and bookmark depth.

rust
// Concatenate each source's bookmarks under a top-level entry (navigation TOC).
// Page labels preserve each section's original numbering.
let merger = merger
    .with_bookmarks(BookmarkMergeStrategy::Concat)
    .with_page_labels(true);
3

Merge and get the document

Document::merge_with_options returns a new Document. The TOC page is inserted at the position specified in the options.

rust
let merged = merger.build()?;
4

Inspect the generated outlines

The merge operation adds bookmark entries that match the TOC. Verify they are present.

rust
for bookmark in merged.outlines()? {
    println!("'{}' -> page {:?}", bookmark.title, bookmark.page);
}
5

Save the merged document

Write the final merged file.

rust
merged.save("merged_with_toc.pdf")?;

Notes and tips

  • The TOC page is generated using the page dimensions of the first page in the merge list. Override with MergeOptions::toc_page_size(PageSize::A4).
  • Existing bookmarks from each source document are preserved and nested under the top-level chapter bookmark.
  • If a source document is encrypted, decrypt it before passing to MergeInput or pass the password with MergeInput::with_password(pw).
  • Named destinations from each source document are re-scoped with a per-document prefix to avoid collisions.

Why PDFluent for this

Pure Rust

No JVM, no runtime, no DLL dependencies. Ships as a single native binary or WASM module.

Memory safe

Rust's ownership model prevents buffer overflows and use-after-free. No segfaults in PDF parsing.

Runs anywhere

Same code runs server-side, in Docker, on AWS Lambda, on Cloudflare Workers, or in the browser via WASM.

Frequently asked questions