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
Build it step by step.
-
1Install the NuGet packages
SelectPdf.Universalis the library. The native package carries the Chromium engine for one platform: add the one for the platform you run on.dotnet add package SelectPdf.Universal dotnet add package SelectPdf.Universal.Native.win-x64
dotnet add package SelectPdf.Universal dotnet add package SelectPdf.Universal.Native.linux-x64
dotnet add package SelectPdf.Universal dotnet add package SelectPdf.Universal.Native.linux-arm64
dotnet add package SelectPdf.Universal dotnet add package SelectPdf.Universal.Native.osx-arm64
-
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.cswith the code below.dotnet new console -n PdfSample cd PdfSample
-
3Create two PDFs to merge
// 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,
HtmlToPdfproducesa.pdfandb.pdf. In your application these are the files you already have. -
4Append them into a new document
// merge: append both files into a new document PdfDocument merged = new PdfDocument(); merged.Append("a.pdf"); merged.Append("b.pdf");Start with an empty
PdfDocumentand callAppendonce per file, in the order the pages should appear.Appendalso accepts a stream or anotherPdfDocument. -
5Save the merged file
merged.Save("merged.pdf"); merged.Close();Savewritesmerged.pdfwith the pages of both inputs;Closereleases it. -
6Run it
dotnet run
The program writes
merged.pdfto the working folder. Without a license key, every page carries a trial watermark; setGlobalProperties.LicenseKeybefore the first conversion to remove it.
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.
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
The PDF it produces.
Download merged.pdf ↓ the file this sample produced, unedited
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.
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.
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.
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();
}
}
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.
Questions, answered.
Short answers about this guide. The SelectPdf.Universal documentation covers every class and option.