§HOW-TO GUIDE · C# · .NET 8 & 10

Merge PDF files in C#: files, streams and folders

Create an empty PdfDocument and call Append for each file, stream or document you want to add, in order.

Compiled and run: Linux ARM64 · Linux x64 · macOS ARM64 · Windows x64

Video walkthrough · 2:07 · Watch on YouTube · Short · 0:24
§STEP BY STEP

Build it step by step.

  1. 1Install the NuGet packages

    SelectPdf.Universal is the library. The native package carries the Chromium engine for one platform: add the one for the platform you run on.

    terminal
    dotnet add package SelectPdf.Universal
    dotnet add package SelectPdf.Universal.Native.win-x64
    terminal
    dotnet add package SelectPdf.Universal
    dotnet add package SelectPdf.Universal.Native.linux-x64
    terminal
    dotnet add package SelectPdf.Universal
    dotnet add package SelectPdf.Universal.Native.linux-arm64
    terminal
    dotnet add package SelectPdf.Universal
    dotnet add package SelectPdf.Universal.Native.osx-arm64
  2. 2Create a console app

    Any .NET 8 or .NET 10 project works. To follow along, create a console app, run the install commands above inside its folder, then replace Program.cs with the code below.

    terminal
    dotnet new console -n PdfSample
    cd PdfSample
  3. 3Create two PDFs to merge

    Program.cs · lines 3–10
    // two source PDFs
    HtmlToPdf converter = new HtmlToPdf();
    PdfDocument a = converter.ConvertHtmlString("<h1>Cover letter</h1>");
    a.Save("a.pdf");
    a.Close();
    PdfDocument b = converter.ConvertHtmlString("<h1>Contract</h1>");
    b.Save("b.pdf");
    b.Close();

    To keep the sample self-contained, HtmlToPdf produces a.pdf and b.pdf. In your application these are the files you already have.

  4. 4Append them into a new document

    Program.cs · lines 12–15
    // merge: append both files into a new document
    PdfDocument merged = new PdfDocument();
    merged.Append("a.pdf");
    merged.Append("b.pdf");

    Start with an empty PdfDocument and call Append once per file, in the order the pages should appear. Append also accepts a stream or another PdfDocument.

  5. 5Save the merged file

    Program.cs · lines 16–17
    merged.Save("merged.pdf");
    merged.Close();

    Save writes merged.pdf with the pages of both inputs; Close releases it.

  6. 6Run it

    terminal
    dotnet run

    The program writes merged.pdf to the working folder. Without a license key, every page carries a trial watermark; set GlobalProperties.LicenseKey before the first conversion to remove it.

§COMPLETE PROGRAM

The complete Program.cs.

This is the exact file our samples pipeline compiled and ran on Linux ARM64 and Linux x64 and macOS ARM64 and Windows x64 to produce the output on this page.

Program.cs
using SelectPdf.Universal;

// two source PDFs
HtmlToPdf converter = new HtmlToPdf();
PdfDocument a = converter.ConvertHtmlString("<h1>Cover letter</h1>");
a.Save("a.pdf");
a.Close();
PdfDocument b = converter.ConvertHtmlString("<h1>Contract</h1>");
b.Save("b.pdf");
b.Close();

// merge: append both files into a new document
PdfDocument merged = new PdfDocument();
merged.Append("a.pdf");
merged.Append("b.pdf");
merged.Save("merged.pdf");
merged.Close();

Open the project on GitHub → this program and the variations below, ready to dotnet run

§OUTPUT

The PDF it produces.

First page of merged.pdf, the PDF this sample produces
merged.pdf, page 1

Download merged.pdf ↓ the file this sample produced, unedited

§VARIATIONS

Variations you will need next.

Each variation is a separate file in the same project, compiled and run with the program above.

Merge PDFs from byte arrays or streams

Uploads, database columns and blob storage hand you bytes, not files. Wrap each one in a MemoryStream and pass it to Append. Keep the streams open until Save has run. Save() without a file name returns the merged PDF as a byte[], ready to send as an HTTP response.

MergeInMemory.cs
using SelectPdf.Universal;

static class MergeInMemory
{
    public static void Run()
    {
        // PDFs that arrive as bytes: uploads, a database column, a blob store
        List<byte[]> files = [File.ReadAllBytes("a.pdf"), File.ReadAllBytes("b.pdf")];

        PdfDocument merged = new PdfDocument();
        List<MemoryStream> streams = [];
        foreach (byte[] file in files)
        {
            MemoryStream stream = new MemoryStream(file);
            streams.Add(stream);
            merged.Append(stream);
        }

        // Save() without a file name returns the merged PDF as bytes, ready for an HTTP response
        byte[] result = merged.Save();
        merged.Close();
        streams.ForEach(s => s.Dispose());

        File.WriteAllBytes("merged-in-memory.pdf", result);
    }
}

Merge converted HTML without temporary files

ConvertHtmlString returns a PdfDocument, and Append takes one directly, so a cover page and a body produced by separate conversions merge without touching the disk. Close the source documents after the merged one is saved, not before.

MergeConvertedPages.cs
using SelectPdf.Universal;

static class MergeConvertedPages
{
    public static void Run()
    {
        // convert straight into documents, no temporary files
        HtmlToPdf converter = new HtmlToPdf();
        PdfDocument cover = converter.ConvertHtmlString("<h1>Annual report</h1><p>Cover page</p>");
        PdfDocument body = converter.ConvertHtmlString("<h2>Results</h2><p>Revenue grew in every region.</p>");

        PdfDocument report = new PdfDocument();
        report.Append(cover);
        report.Append(body);
        report.Save("report.pdf");

        // appended documents must stay open until the merged document is saved
        report.Close();
        cover.Close();
        body.Close();
    }
}

Merge every PDF in a folder

Directory.GetFiles lists the PDFs and Order puts them in file name order, so a numeric or date prefix in the file names controls the page order of the result.

MergeFolder.cs
using SelectPdf.Universal;

static class MergeFolder
{
    public static void Run()
    {
        // a folder of PDFs (made here so the sample runs on its own)
        Directory.CreateDirectory("inbox");
        HtmlToPdf converter = new HtmlToPdf();
        foreach (string month in new[] { "01-january", "02-february", "03-march" })
        {
            PdfDocument statement = converter.ConvertHtmlString($"<h1>Statement {month}</h1>");
            statement.Save(Path.Combine("inbox", $"{month}.pdf"));
            statement.Close();
        }

        // merge every PDF in the folder, in file name order
        string[] files = Directory.GetFiles("inbox", "*.pdf").Order(StringComparer.Ordinal).ToArray();

        PdfDocument merged = new PdfDocument();
        foreach (string file in files)
            merged.Append(file);

        merged.Save("statements-q1.pdf");
        merged.Close();
    }
}
§TROUBLESHOOTING

Common problems and fixes.

Pages are missing or the merged file is broken

A document or stream passed to Append is read when the merged document is saved. Close source documents and dispose streams only after Save.

A source PDF is password-protected

Open it with new PdfDocument(fileName, password) (or the stream overload) and append the opened document.

The merged PDF must be PDF/A

Create the target with new PdfDocument(PdfStandard.PdfA2B) or another level. Appending a document that does not conform to that level throws PdfDocumentException.

§GOOD TO KNOW

Good to know.

  • PdfDocument.Append accepts a PdfDocument, a file name or a stream. Source
  • A document passed to Append must stay open until the merged document is saved. Source
  • For PDF/A output, create the target with new PdfDocument(PdfStandard.PdfA2B) or another level and append to it. Source
?FAQ

Questions, answered.

Short answers about this guide. The SelectPdf.Universal documentation covers every class and option.

Create an empty PdfDocument, call Append with the first file name, then the second, and Save. Pages are added in the order you append them.
Yes. Append accepts a Stream, so wrap each byte array in a MemoryStream. Save() without arguments returns the merged PDF as a byte array.
Yes. SelectPdf.Universal runs the same code on Windows, Linux x64 and ARM64, and macOS on Apple Silicon. Only the native package you reference differs.
No. The Community Edition is HTML to PDF only. Merging is part of the commercial SelectPdf.Universal library, which you can try for free with a watermark.