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

HTML headers and footers with a logo and the date in C#

Turn on the converter's header and footer, then add a PdfHtmlSection to each: any HTML and CSS, here an SVG logo with a title at the top and the generation date at the bottom. They repeat on every page of the converted document.

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

Video walkthrough · 3:19 · Watch on YouTube · Short · 0:50
§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. 3Turn on the header

    Program.cs · lines 3–7
    HtmlToPdf converter = new HtmlToPdf();
    
    // header: a logo and a title, written in HTML
    converter.Options.DisplayHeader = true;
    converter.Header.Height = 60;

    Options.DisplayHeader enables the header, and Header.Height reserves 60 points at the top of every page for it.

  4. 4Write the header in HTML

    Program.cs · lines 8–17
    string header = """
        <div style="display: flex; align-items: center; gap: 16px; padding: 16px 40px;
                    border-bottom: 3px solid #c2410c; font-family: sans-serif">
          <svg width="48" height="48" font-family="sans-serif" font-weight="bold" font-size="24">
            <rect width="48" height="48" rx="10" fill="#c2410c"/>
            <text x="24" y="33" fill="white" text-anchor="middle">A</text></svg>
          <span style="font-size: 28px; font-weight: bold">Acme Ltd. Sales report</span>
        </div>
        """;
    converter.Header.Add(new PdfHtmlSection(header, ""));

    The header is ordinary HTML and CSS: an inline SVG logo and the report title side by side with flexbox, above an orange rule. The logo is part of the HTML, so the sample needs no image file and no network. new PdfHtmlSection(header, "") takes the HTML string and a base URL for relative paths (none here); Header.Add attaches it.

  5. 5Put the date in the footer

    Program.cs · lines 19–24
    // footer: the date the report was generated
    converter.Options.DisplayFooter = true;
    converter.Footer.Height = 40;
    string today = DateTime.Today.ToLongDateString();
    converter.Footer.Add(new PdfHtmlSection(
        $"<div style='padding: 16px 40px; font: 20px sans-serif; color: #555'>Generated on {today}</div>", ""));

    Options.DisplayFooter and Footer.Height do the same at the bottom of the page. The footer is another PdfHtmlSection, with the date built in C# and placed in the HTML. Font sizes look large because the section's HTML is laid out 1024px wide and scaled down to the width of the page.

  6. 6Convert a report that spans pages

    Program.cs · lines 26–32
    // a report long enough for several pages
    string rows = string.Concat(Enumerable.Range(1, 150).Select(i =>
        $"<tr><td>Order {1000 + i}</td><td>{i * 7 % 90 + 10} units</td></tr>"));
    PdfDocument doc = converter.ConvertHtmlString(
        $"<body style='margin: 0 40px; font-family: sans-serif'><h1>Orders</h1><table width='400'>{rows}</table></body>");
    doc.Save("report.pdf");
    doc.Close();

    150 orders fill several pages. ConvertHtmlString renders the report, the header and footer are drawn on every page, and report.pdf is saved.

  7. 7Run it

    terminal
    dotnet run

    The program writes report.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;

HtmlToPdf converter = new HtmlToPdf();

// header: a logo and a title, written in HTML
converter.Options.DisplayHeader = true;
converter.Header.Height = 60;
string header = """
    <div style="display: flex; align-items: center; gap: 16px; padding: 16px 40px;
                border-bottom: 3px solid #c2410c; font-family: sans-serif">
      <svg width="48" height="48" font-family="sans-serif" font-weight="bold" font-size="24">
        <rect width="48" height="48" rx="10" fill="#c2410c"/>
        <text x="24" y="33" fill="white" text-anchor="middle">A</text></svg>
      <span style="font-size: 28px; font-weight: bold">Acme Ltd. Sales report</span>
    </div>
    """;
converter.Header.Add(new PdfHtmlSection(header, ""));

// footer: the date the report was generated
converter.Options.DisplayFooter = true;
converter.Footer.Height = 40;
string today = DateTime.Today.ToLongDateString();
converter.Footer.Add(new PdfHtmlSection(
    $"<div style='padding: 16px 40px; font: 20px sans-serif; color: #555'>Generated on {today}</div>", ""));

// a report long enough for several pages
string rows = string.Concat(Enumerable.Range(1, 150).Select(i =>
    $"<tr><td>Order {1000 + i}</td><td>{i * 7 % 90 + 10} units</td></tr>"));
PdfDocument doc = converter.ConvertHtmlString(
    $"<body style='margin: 0 40px; font-family: sans-serif'><h1>Orders</h1><table width='400'>{rows}</table></body>");
doc.Save("report.pdf");
doc.Close();

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

§OUTPUT

The PDF it produces.

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

Download report.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.

Load the header from an HTML file or URL

new PdfHtmlSection(url) renders a web page into the header. Keep the header in its own HTML file next to your other templates and pass its file:// URL, or pass an https:// URL to a header your web app serves.

HeaderFromFile.cs
using SelectPdf.Universal;

static class HeaderFromFile
{
    public static void Run()
    {
        // the header lives in its own HTML file, maintained with the rest of your templates
        File.WriteAllText("header.html", """
            <div style="padding: 16px 40px; border-bottom: 3px solid #c2410c; font: bold 28px sans-serif">
              Acme Ltd. Sales report
            </div>
            """);

        HtmlToPdf converter = new HtmlToPdf();
        converter.Options.DisplayHeader = true;
        converter.Header.Height = 60;

        // a section built from a URL renders that page: here a file:// URL of the local file
        string url = new Uri(Path.GetFullPath("header.html")).AbsoluteUri;
        converter.Header.Add(new PdfHtmlSection(url));

        string rows = string.Concat(Enumerable.Range(1, 120).Select(i => $"<p>Line item {i}</p>"));
        PdfDocument doc = converter.ConvertHtmlString($"<h1>Report</h1>{rows}");
        doc.Save("header-from-file.pdf");
        doc.Close();
    }
}

Use a PNG logo with an image section

PdfImageSection draws an image from a stream or a file at a position in the header; given only a width, it keeps the image's proportions. A PdfTextSection next to it adds the title, without any HTML.

ImageLogo.cs
using SelectPdf.Universal;

static class ImageLogo
{
    public static void Run()
    {
        // a PNG logo; in your application read it from a file or an embedded resource
        byte[] logo = Convert.FromBase64String(
            "iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKklEQVR42mM45MhDU8QwasGoBaMWjFowasGoBaMWjFowasGoBaMWDBULACVwPD2wuxyUAAAAAElFTkSuQmCC");

        HtmlToPdf converter = new HtmlToPdf();
        converter.Options.DisplayHeader = true;
        converter.Header.Height = 50;

        // image section: 30 points wide, height kept in proportion
        converter.Header.Add(new PdfImageSection(20, 10, 30, new MemoryStream(logo)));

        // text section next to it
        PdfTextSection title = new PdfTextSection(60, 18, "Acme Ltd. Sales report",
            PdfFont.CreateFromSystemFont("Liberation Sans", 14f));
        converter.Header.Add(title);

        string rows = string.Concat(Enumerable.Range(1, 120).Select(i => $"<p>Line item {i}</p>"));
        PdfDocument doc = converter.ConvertHtmlString($"<h1>Report</h1>{rows}");
        doc.Save("image-logo.pdf");
        doc.Close();
    }
}

A different header on the first page

After the conversion, PdfPage.CustomHeader replaces the document header on one page. The template from doc.AddTemplate is as wide as the page and as tall as Header.Height, and a PdfHtmlElement fills it with a colored title band; every other page keeps the plain header.

FirstPageHeader.cs
using SelectPdf.Universal;

static class FirstPageHeader
{
    public static void Run()
    {
        HtmlToPdf converter = new HtmlToPdf();
        converter.Options.DisplayHeader = true;
        converter.Header.Height = 60;
        converter.Header.Add(new PdfHtmlSection(
            "<div style='padding: 20px 40px; font: 20px sans-serif; color: #555'>Acme Ltd. Sales report</div>", ""));

        string rows = string.Concat(Enumerable.Range(1, 120).Select(i => $"<p>Line item {i}</p>"));
        PdfDocument doc = converter.ConvertHtmlString($"<h1>Report</h1>{rows}");

        // page 1 gets its own header: a template as wide as the page and as tall as the header
        PdfPage first = doc.Pages[0];
        PdfTemplate cover = doc.AddTemplate(first.PageSize.Width, 60);
        cover.Add(new PdfHtmlElement(
            "<div style='padding: 12px 40px; background: #c2410c; color: white; font: bold 32px sans-serif'>" +
            "Acme Ltd. Sales report</div>", ""));
        first.CustomHeader = cover;

        doc.Save("first-page-header.pdf");
        doc.Close();
    }
}
§TROUBLESHOOTING

Common problems and fixes.

The header or footer does not appear

Adding sections to converter.Header is not enough: converter.Options.DisplayHeader must be true too, and Options.DisplayFooter for the footer.

The text in the header is too small

The section's HTML is laid out at a web page width of 1024px and scaled to the width of the page, so CSS sizes come out smaller in the PDF. Use larger sizes in the HTML, or a smaller WebPageWidth on the PdfHtmlSection.

The header HTML does not fit in the header

Header.Height is the height of the header in points. Make it tall enough for the scaled HTML, padding included; Footer.Height sets the footer the same way.

Images with relative paths do not load

The second argument of new PdfHtmlSection(html, baseUrl) is the base URL used to resolve relative paths in the HTML string. Pass the folder or site the images come from, or embed them in the HTML as the logo here is.

§GOOD TO KNOW

Good to know.

  • Headers and footers can hold text (PdfTextSection), images (PdfImageSection) and HTML (PdfHtmlSection). Source
  • If Options.DisplayHeader is false, the header is not displayed and all the options set for it have no effect. The footer works the same way with Options.DisplayFooter. Source
  • A PdfHtmlSection is created from a URL or from an HTML string, with a base URL that resolves relative paths in the HTML. Source
  • The section's HTML is laid out at a web page width of 1024px by default and scaled so that this width fills the width of the section. Source
  • After the conversion, a single page can replace the document header with its own template through PdfPage.CustomHeader, or hide it with PdfPage.DisplayHeader. Source
?FAQ

Questions, answered.

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

Set converter.Options.DisplayHeader to true, give converter.Header a height, and add a PdfHtmlSection with HTML that contains the logo and the title. It is drawn at the top of every page.
Yes. new PdfHtmlSection(url) renders that page into the header or footer. Pass an HTML string and a base URL instead when you build the HTML in code.
Set Header.DisplayOnFirstPage to false to hide it there, or, after the conversion, set doc.Pages[0].CustomHeader to a template made with doc.AddTemplate to replace it on that page only.
Add a PdfTextSection to the same header or footer with {page_number} and {total_pages} in its text. Both placeholders are replaced on every page.