Page numbers transform PDFs from disconnected pages into navigable documents. Without them, users reference content by searching keywords or scrolling endlessly. With page numbers, they cite specific pages in emails ("see page 47"), jump to sections using table-of-contents links, and understand document length at a glance. I've built invoice systems generating thousands of multi-page PDFs monthly where page numbers enabled customer service to reference line items by page, legal teams to cite contract clauses, and automated systems to extract specific pages for processing.
The challenge is that many PDF libraries treat page numbering as an afterthought requiring complex event handlers or manual text positioning. Stack Overflow's top answers for this task recommend iTextSharp's PdfPageEventHelper approach — inheriting from event classes, overriding OnEndPage(), manually drawing text at calculated coordinates. These answers from 2012-2014 still rank highly despite being locked by moderators and promoting unnecessarily complex solutions for what should be simple header/footer configuration.
IronPDF uses placeholder strings {page} and {total-pages} in text or HTML headers/footers. No event handlers. No coordinate calculations. No inheritance. Set pdf.AddTextHeaders(new TextHeaderFooter { CenterText = "{page} of {total-pages}" }) and page numbers appear automatically. For HTML-based styling — custom fonts, colors, positioning — use HtmlHeaderFooter with full CSS control.
Understanding page number implementation prevents common mistakes. Page numbers are rendered as headers or footers during PDF generation or applied to existing PDFs post-creation. The rendering engine replaces {page} with the current page number (1, 2, 3...) and {total-pages} with the document's total page count. This happens at render time, ensuring accuracy even when merging PDFs or modifying page counts.
using IronPdf;
// Install via NuGet: Install-Package IronPdf
var renderer = new ChromePdfRenderer();
var pdf = renderer.RenderHtmlAsPdf("<h1>Contract Agreement</h1><p>Terms and conditions...</p>");
var footer = new TextHeaderFooter
{
CenterText = "Page {page} of {total-pages}",
FontSize = 10
};
pdf.AddTextFooters(footer);
pdf.SaveAs("contract-with-page-numbers.pdf");
This adds centered page numbers to the footer of every page. The {page} and {total-pages} placeholders dynamically insert correct values. For a 12-page document, page 7 displays "Page 7 of 12" automatically.
What NuGet Packages Do I Need?
Install IronPDF via NuGet Package Manager Console:
Install-Package IronPdf
Or via .NET CLI:
dotnet add package IronPdf
IronPDF includes page numbering functionality in the core library. No additional packages required for headers, footers, or page number placeholders.
How Do I Add Page Numbers During PDF Creation?
Apply page numbers at render time by configuring ChromePdfRenderer.RenderingOptions:
var renderer = new ChromePdfRenderer();
renderer.RenderingOptions.TextFooter = new TextHeaderFooter
{
RightText = "Page {page}",
FontSize = 9
};
var html = @"
<h1>Annual Report 2025</h1>
<p>Executive summary...</p>
<div style='page-break-after: always;'></div>
<h2>Financial Results</h2>
<p>Revenue and expenses...</p>
";
var pdf = renderer.RenderHtmlAsPdf(html);
pdf.SaveAs("report-with-page-numbers.pdf");
This renders the HTML into a PDF with page numbers already applied. The footer appears on all pages, right-aligned, showing "Page 1", "Page 2", etc. Using RenderingOptions is efficient when generating new PDFs — you configure once and all rendered documents include page numbers.
I use this approach in automated invoice generation where templates render hundreds of PDFs nightly. Setting RenderingOptions.TextFooter once applies page numbers to every generated invoice without per-document configuration.
How Do I Add Page Numbers to an Existing PDF?
Load an existing PDF and apply page numbers using AddTextFooters() or AddHtmlFooters():
var existingPdf = PdfDocument.FromFile("report-without-numbers.pdf");
var footer = new TextHeaderFooter
{
CenterText = "- {page} -",
FontSize = 10
};
existingPdf.AddTextFooters(footer);
existingPdf.SaveAs("report-with-numbers.pdf");
This works for PDFs generated by any source — scanned documents, PDFs from external systems, legacy files created years ago. The page numbers overlay on existing content, positioned in the footer margin to avoid overlapping text.
I've bulk-processed archival PDFs lacking page numbers before uploading to document management systems. Loading each file, applying consistent numbering, and saving takes seconds per file — efficient even for thousands of documents.
What's the Difference Between TextHeaderFooter and HtmlHeaderFooter?
TextHeaderFooter provides simple, unformatted text headers and footers. Configure positioning (left, center, right), font size, and placeholder strings. Fast and sufficient for basic page numbering.
var textFooter = new TextHeaderFooter
{
LeftText = "Confidential",
CenterText = "Page {page} of {total-pages}",
RightText = "© 2025 Acme Corp",
FontSize = 9
};
This creates a three-part footer: "Confidential" on left, page numbers centered, copyright on right.
HtmlHeaderFooter supports full HTML and CSS styling — custom fonts, colors, backgrounds, images, complex layouts:
var htmlFooter = new HtmlHeaderFooter
{
HtmlFragment = @"
<div style='text-align: center; font-family: Georgia; color: #666; font-size: 10pt;'>
<span style='font-style: italic;'>Page {page} of {total-pages}</span>
</div>
"
};
pdf.AddHtmlFooters(htmlFooter);
This renders italicized page numbers in Georgia font, gray color, centered. Use HtmlHeaderFooter when branding requires specific fonts, colors, or when embedding logos alongside page numbers.
For most business documents — contracts, invoices, reports — TextHeaderFooter suffices. For marketing materials, branded templates, or documents with strict style guides, HtmlHeaderFooter provides design flexibility.
How Do I Skip the Cover Page?
Cover pages, title pages, and table-of-contents pages often shouldn't have page numbers. Skip them using page index filtering:
var pdf = renderer.RenderHtmlAsPdf(htmlWithCoverPage);
var footer = new HtmlHeaderFooter
{
HtmlFragment = "<center>Page {page}</center>"
};
// Apply to all pages except first (index 0)
var pageIndices = Enumerable.Range(0, pdf.PageCount).Skip(1);
pdf.AddHtmlFooters(footer, firstPageNumber: 1, pageIndices: pageIndices);
pdf.SaveAs("document-skip-cover.pdf");
The Skip(1) removes the first page (index 0) from the range. Page numbers start appearing on page 2. The firstPageNumber: 1 parameter ensures page 2 displays "Page 1" rather than "Page 2" — useful when the cover doesn't count toward pagination.
I've generated technical documentation where the first 3 pages (cover, copyright, table of contents) lack page numbers, with numbering starting on page 4 as "Page 1". This is common in book publishing and formal reports.
Can I Number Only Odd or Even Pages?
Yes, using LINQ to filter page indices:
var pdf = renderer.RenderHtmlAsPdf(html);
var allPages = Enumerable.Range(0, pdf.PageCount);
// Odd page indexes (even page numbers: 2, 4, 6...)
var oddIndexes = allPages.Where(i => i % 2 != 0);
// Even page indexes (odd page numbers: 1, 3, 5...)
var evenIndexes = allPages.Where(i => i % 2 == 0);
var footer = new TextHeaderFooter { CenterText = "Page {page}" };
// Number only odd page numbers (even indexes)
pdf.AddTextFooters(footer, pageIndices: oddIndexes);
This pattern is useful for double-sided printing where page numbers should appear only on right-hand pages (odd numbers) or only on left-hand pages (even numbers). Some formal documents follow this convention for aesthetic reasons.
How Does IronPDF Compare to iTextSharp for Page Numbering?
iTextSharp (and its successor iText7) require event handler inheritance to add page numbers. Here's the typical Stack Overflow-recommended approach:
// iTextSharp approach (complex)
public class PageEventHelper : PdfPageEventHelper
{
public override void OnEndPage(PdfWriter writer, Document document)
{
var cb = writer.DirectContent;
var text = $"Page {writer.PageNumber}";
var textSize = BaseFont.CreateFont().GetWidthPoint(text, 10);
cb.BeginText();
cb.SetFontAndSize(BaseFont.CreateFont(), 10);
cb.SetTextMatrix(document.PageSize.Width / 2 - textSize / 2, 30);
cb.ShowText(text);
cb.EndText();
}
}
// Then attach event handler to writer
writer.PageEvent = new PageEventHelper();
This requires understanding PdfPageEventHelper, PdfWriter, DirectContent, manual coordinate calculations, and font metrics. It's 15+ lines of boilerplate for simple page numbering.
IronPDF accomplishes the same in 3 lines:
var footer = new TextHeaderFooter { CenterText = "Page {page}" };
pdf.AddTextFooters(footer);
I migrated a contract management system from iTextSharp to IronPDF specifically because page numbering logic became unmaintainable. Developers unfamiliar with PDF internals couldn't modify positioning or formatting without breaking coordinate calculations. Switching to IronPDF's declarative headers/footers eliminated this complexity.
What About Bates Numbering for Legal Documents?
Bates numbering assigns unique identifiers to pages for legal discovery, medical records, and archival systems. Format is typically a prefix + sequential number: DOC001-0001, DOC001-0002, etc.
Implement Bates numbering using custom page number formatting:
var pdf = PdfDocument.FromFile("legal-discovery.pdf");
for (int i = 0; i < pdf.PageCount; i++)
{
var batesNumber = $"CASE2025-{(i + 1):D4}"; // CASE2025-0001, CASE2025-0002...
var footer = new TextHeaderFooter
{
RightText = batesNumber,
FontSize = 8
};
pdf.AddTextFooters(footer, pageIndices: new[] { i });
}
pdf.SaveAs("bates-numbered.pdf");
This assigns unique Bates stamps to each page. The :D4 format specifier pads numbers with zeros (0001, 0002, ..., 0147). Right-aligned positioning is conventional for Bates numbers.
I've processed litigation document dumps requiring Bates stamping across 10,000+ PDFs. Automated scripts applied sequential numbering across all files, ensuring no duplicates and maintaining proper sequencing.
What Common Issues Should I Watch For?
Margins and overlapping content: If page numbers overlap document content, adjust footer margins. Use RenderingOptions.MarginBottom to reserve space:
renderer.RenderingOptions.MarginBottom = 25; // millimeters
This pushes content up, creating footer space for page numbers without overlap.
First/last page only: Some documents need page numbers only on the last page (summaries, certificates). Use pageIndices: new[] { pdf.PageCount - 1 } to target the final page.
Dynamic total-pages in merged PDFs: When merging PDFs, {total-pages} reflects the combined page count. If merging a 5-page and 3-page PDF, {total-pages} becomes 8. Apply page numbers after merging for accurate totals.
Encoding special characters: If page number text includes special characters (©, ™, accented letters), ensure proper UTF-8 encoding. IronPDF handles this automatically, but legacy systems may have issues.
I've debugged page numbering where numbers overlapped watermarks or logos. Adjusting footer positioning using HTML styling (margin-top, padding) in HtmlHeaderFooter resolved conflicts without modifying document content.
Quick Reference
| Approach | Code | Use Case |
|---|---|---|
| Simple text footer | new TextHeaderFooter { CenterText = "Page {page}" } |
Basic page numbers, no styling |
| HTML styled footer | new HtmlHeaderFooter { HtmlFragment = "<center><i>{page}</i></center>" } |
Custom fonts, colors, branding |
| Page X of Y format | CenterText = "{page} of {total-pages}" |
Show progress through document |
| Skip cover page | pageIndices: Enumerable.Range(0, pdf.PageCount).Skip(1) |
Exclude title/cover pages |
| Odd/even pages only | pageIndices: allPages.Where(i => i % 2 == 0) |
Double-sided printing layouts |
| First page only | pageIndices: new[] { 0 } |
Single-page numbering |
| Last page only | pageIndices: new[] { pdf.PageCount - 1 } |
Summary pages, certificates |
| Bates numbering | Custom loop with $"PREFIX-{(i+1):D4}"
|
Legal discovery, medical records |
Key Principles:
- Use
{page}and{total-pages}placeholders for dynamic numbering -
TextHeaderFooterfor simple text,HtmlHeaderFooterfor styled content - Apply during rendering (
RenderingOptions) or post-creation (AddTextFooters()) - Filter page indices using LINQ (
Skip,Where,Take) for selective numbering - IronPDF's approach is dramatically simpler than iTextSharp's event handler pattern
- Reserve footer margin space if numbers overlap content
- Bates numbering uses sequential custom identifiers per page
The complete page numbers guide includes examples for advanced positioning, multi-section numbering, and Roman numeral formats.
Written by Jacob Mellor, CTO at Iron Software. Jacob created IronPDF and leads a team of 50+ engineers building .NET document processing libraries.
Top comments (0)