---
title: "Split a PDF in C#"
url: https://recursiveai.org/onepdf/articles/split-pdf-csharp/
description: "Split a PDF in C# into single pages, fixed-size parts, page ranges or one file per bookmark, keeping bookmarks, links and form fields. Pure .NET, MIT or Apache-2.0."
date: 2026-10-26
author: OnePDF team
---

# Split a PDF in C#

Split a PDF in C# into single pages, fixed-size parts, page ranges or one file per bookmark, keeping bookmarks, links and form fields. Pure .NET, MIT or Apache-2.0.

October 26, 2026 · OnePDF team

To split a PDF in C#, open it with OnePDF and call `PdfDocument.Split`, either with a page count per part or with the page ranges you want. Each part is a new document in memory: save it to a file or a stream and dispose it. Bookmarks, links and form fields go with the pages they belong to. OnePDF is pure C# with no native binaries, licensed MIT OR Apache-2.0. This page splits into single pages, fixed-size parts, chosen ranges and one file per chapter bookmark, and returns pages as a zip from memory. Every sample was compiled and run against OnePDF 1.11.0 on .NET 10, and every output passes `qpdf --check`.

## Before you start

*Terminal · NuGet*

```
dotnet new console -o PdfSplit
cd PdfSplit
dotnet add package RecursiveAI.OnePdf --version 1.11.0
```

OnePDF targets .NET 10. The samples split `handbook.pdf`, six pages in three chapters of two, three and one pages, with a bookmark for each chapter and each section. To make it, run this once with `RecursiveAI.OnePdf.Layout` added, or use a PDF of your own:

*MakeHandbook.cs · OnePdf.Layout 1.11.0*

```
using OnePdf.Layout;
 
// A handbook with three chapters of 2, 3 and 1 pages, a bookmark per chapter and per section.
(string Title, int Pages)[] chapters = [("Getting started", 2), ("Safety on the quay", 3), ("Contacts", 1)];
 
LayoutDocument.Compose(doc =>
{
    doc.Title("Harbor handbook").Language("en-US");
    doc.Page(page =>
    {
        page.Size(PageSize.Letter);
        page.Margin(72);
        page.Content().Column(col =>
        {
            col.Spacing(12);
            for (int c = 0; c < chapters.Length; c++)
            {
                if (c > 0) col.Item().PageBreak();
                col.Item().Bookmark(chapters[c].Title).Heading(1).Text(chapters[c].Title).FontSize(22).Bold();
                for (int p = 1; p <= chapters[c].Pages; p++)
                {
                    if (p > 1) col.Item().PageBreak();
                    col.Item().Bookmark($"Section {c + 1}.{p}", level: 1).Heading(2).Text($"Section {c + 1}.{p}").FontSize(15).Bold();
                    col.Item().Text($"Text of section {c + 1}.{p}.");
                }
            }
        });
    });
}).Save("handbook.pdf");
```

## Split into single pages or fixed-size parts

*Program.cs · OnePDF 1.11.0 · .NET 10*

```
using OnePdf;
 
using PdfDocument doc = PdfDocument.Open("handbook.pdf");
 
// One file per page.
IReadOnlyList<PdfDocument> pages = PdfDocument.Split(doc, pagesPerPart: 1);
for (int i = 0; i < pages.Count; i++)
{
    using PdfDocument part = pages[i];
    part.Save($"handbook-page-{i + 1}.pdf");
}
Console.WriteLine($"{pages.Count} single-page files");
 
// Fixed-size parts: 4 pages each, the last one shorter.
IReadOnlyList<PdfDocument> chunks = PdfDocument.Split(doc, pagesPerPart: 4);
for (int i = 0; i < chunks.Count; i++)
{
    using PdfDocument part = chunks[i];
    part.Save($"handbook-part-{i + 1}.pdf");
    Console.WriteLine($"handbook-part-{i + 1}.pdf: {part.PageCount} pages");
}
```

*Output · Console*

```
6 single-page files
handbook-part-1.pdf: 4 pages
handbook-part-2.pdf: 2 pages
```

`Split(doc, pagesPerPart: n)` cuts the document into parts of `n` pages, and the last part takes what is left. The source only needs to be open for reading. Each part is independent of it, so dispose each one when it is saved, as the `using` inside the loop does.

## Extract a range of pages

To pull out some pages, pass the ranges you want. `PdfPageRange` takes a zero-based start page and a count, so pages 3 to 5 are `new PdfPageRange(2, 3)`:

*Program.cs · OnePDF 1.11.0 · .NET 10*

```
using OnePdf;
 
using PdfDocument doc = PdfDocument.Open("handbook.pdf");
 
// Extract pages 3 to 5 (zero-based start 2, count 3) into a new file.
IReadOnlyList<PdfDocument> parts = PdfDocument.Split(doc, [new PdfPageRange(2, 3)]);
using PdfDocument safety = parts[0];
safety.SetInfo("Title", "Harbor handbook: safety on the quay");   // parts carry no metadata from the source
safety.Save("safety.pdf");
 
Console.WriteLine($"safety.pdf: {safety.PageCount} pages");
foreach (PdfOutlineItem item in safety.Outline)
    Console.WriteLine($"  {item.Title} ({item.Children.Count} sections)");
```

*Output · Console*

```
safety.pdf: 3 pages
  Safety on the quay (3 sections)
```

The extracted chapter keeps its bookmark and the three section bookmarks under it. Parts carry no title, author or other metadata from the source, so set what you need with `SetInfo`. Pass several ranges to get several files in one call. Ranges may overlap, for example to give each part a shared cover page.

## Split a PDF by bookmarks

Long documents usually have a bookmark per chapter. Each bookmark’s `Destination.PageIndex` tells you where its chapter starts, so a chapter runs from there to the next bookmark’s page:

*Program.cs · OnePDF 1.11.0 · .NET 10*

```
using OnePdf;
 
using PdfDocument doc = PdfDocument.Open("handbook.pdf");
 
// Each top-level bookmark starts a part that runs to the next one.
var starts = doc.Outline
    .Select(item => (item.Title, Page: item.Destination?.PageIndex))
    .Where(s => s.Page is not null)
    .Select(s => (s.Title, Start: s.Page!.Value))
    .OrderBy(s => s.Start)
    .ToList();
 
var ranges = starts.Select((s, i) =>
    new PdfPageRange(s.Start, (i + 1 < starts.Count ? starts[i + 1].Start : doc.PageCount) - s.Start)).ToList();
 
IReadOnlyList<PdfDocument> parts = PdfDocument.Split(doc, ranges);
for (int i = 0; i < parts.Count; i++)
{
    using PdfDocument part = parts[i];
    string file = $"{i + 1:00}-{Slug(starts[i].Title)}.pdf";
    part.SetInfo("Title", starts[i].Title);
    part.Save(file);
    Console.WriteLine($"{file}: page count {part.PageCount}; bookmarks: {string.Join(", ", Flatten(part.Outline).Select(b => b.Title))}");
}
 
static string Slug(string title) =>
    string.Join("-", title.ToLowerInvariant().Split(' ', StringSplitOptions.RemoveEmptyEntries));
 
static IEnumerable<PdfOutlineItem> Flatten(IEnumerable<PdfOutlineItem> items) =>
    items.SelectMany(item => new[] { item }.Concat(Flatten(item.Children)));
```

*Output · Console*

```
01-getting-started.pdf: page count 2; bookmarks: Getting started, Section 1.1, Section 1.2
02-safety-on-the-quay.pdf: page count 3; bookmarks: Safety on the quay, Section 2.1, Section 2.2, Section 2.3
03-contacts.pdf: page count 1; bookmarks: Contacts, Section 3.1
```

Each chapter file has its own bookmark tree. A bookmark whose destination is outside the document, or that has none, is skipped by the `Where`. The same idea splits on anything you can find on a page: run [text extraction](https://recursiveai.org/onepdf/articles/extract-text-from-pdf-csharp/) over each page and start a new part where an invoice number or a heading appears.

## Split a PDF from a byte array and return a zip

In a web API or a queue worker the PDF arrives as bytes. `PdfDocument.Open` takes a `byte[]` (or a `Stream` or `ReadOnlyMemory<byte>`), and each part saves to any stream, here straight into a zip entry:

*Program.cs · OnePDF 1.11.0 · .NET 10*

```
using System.IO.Compression;
using OnePdf;
 
// Split a PDF held in memory (an upload, a blob) and return the pages as a zip.
byte[] zip = SplitToZip(File.ReadAllBytes("handbook.pdf"));
File.WriteAllBytes("handbook-pages.zip", zip);
 
using var archive = new ZipArchive(new MemoryStream(zip));
Console.WriteLine($"{archive.Entries.Count} entries: {string.Join(", ", archive.Entries.Select(e => e.Name))}");
 
static byte[] SplitToZip(byte[] pdf)
{
    using PdfDocument doc = PdfDocument.Open(pdf);
    using var output = new MemoryStream();
    using (var archive = new ZipArchive(output, ZipArchiveMode.Create, leaveOpen: true))
    {
        IReadOnlyList<PdfDocument> parts = PdfDocument.Split(doc, pagesPerPart: 1);
        for (int i = 0; i < parts.Count; i++)
        {
            using PdfDocument part = parts[i];
            using Stream entry = archive.CreateEntry($"page-{i + 1}.pdf").Open();
            part.Save(entry);
        }
    }
    return output.ToArray();
}
```

*Output · Console*

```
6 entries: page-1.pdf, page-2.pdf, page-3.pdf, page-4.pdf, page-5.pdf, page-6.pdf
```

In ASP.NET Core, return the bytes with `Results.File(zip, "application/zip", "pages.zip")`.

## What each part keeps

- **Bookmarks** that lead to the part’s pages, with their nesting.
- **Links** between the part’s own pages, and the named destinations they use. A link to a page in another part keeps its rectangle but loses its target, since that page isn’t in the file.
- **Form fields** whose widgets are on the part’s pages, with the field tree above them.
- **Tagged structure**, **page labels** and **optional content** of the copied pages.
- **Resources** the pages use, such as fonts and images, copied once per part.

Not carried: metadata, signatures (a signature covers the bytes of the original file), document-level JavaScript and permissions. One case makes parts bigger than you’d expect: some files, slide decks especially, give every page one shared resource dictionary holding all the fonts and images. That dictionary is copied whole into each part. The rules are in [page operations](https://recursiveai.org/onepdf/docs/page-operations/).

## Encrypted and damaged PDFs

An encrypted PDF opens with its password through `PdfOpenOptions`, and the parts are written unencrypted unless you set encryption on save. A damaged file is repaired as it is read, so most PDFs that other tools refuse can still be split. See [decrypt a PDF](https://recursiveai.org/onepdf/cookbook/decrypt-pdf/) and [repair a damaged PDF](https://recursiveai.org/onepdf/cookbook/repair-damaged-pdf/).

## Where next

- [Split a PDF](https://recursiveai.org/onepdf/cookbook/split-pdf/), the short cookbook recipe.
- [Merge PDF files in C#](https://recursiveai.org/onepdf/articles/merge-pdf-csharp/), the other direction.
- [Rotate, delete and reorder pages](https://recursiveai.org/onepdf/cookbook/rotate-delete-pages/) in place.
- [Page operations](https://recursiveai.org/onepdf/docs/page-operations/): every rule for copied pages.
