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

Fix blank pages and missing content in HTML to PDF (C#)

A PDF that shows "Loading...", a broken image or a section you can see in the browser but not in the document usually has one of a few causes: the page was captured before its scripts finished, relative links could not be resolved, the page used different CSS for print, or wide content was cut at the right edge. Each has a converter option that fixes it.

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

Video walkthrough · 3:24 · Watch on YouTube · Short · 1:00
§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. 3Import the namespace

    Program.cs · lines 1–1
    using SelectPdf.Universal;

    Everything used here is in SelectPdf.Universal.

  4. 4A page that fills itself in

    Program.cs · lines 3–15
    // A report whose totals are written by a script 1.5 seconds after the page
    // has loaded, the way a chart or a fetch() call fills in a page.
    string html = """
        <h1>Quarterly report</h1>
        <p>Sales for July to September 2026.</p>
        <div id="totals">Loading totals...</div>
        <script>
          setTimeout(() => {
            document.getElementById("totals").innerHTML =
              "<h2>Totals</h2><p>Revenue: 1,284,500 EUR</p><p>Orders: 9,312</p>";
          }, 1500);
        </script>
        """;

    The totals are written by a script 1.5 seconds after the page has loaded, like a chart, a fetch() call or a framework that renders on the client. Converted with the default options, the PDF shows "Loading totals...".

  5. 5Wait for the script

    Program.cs · lines 17–21
    HtmlToPdf converter = new HtmlToPdf();
    
    // By default the page is captured as soon as it has loaded, so the PDF
    // shows "Loading totals...". Wait 3 more seconds for the script to run.
    converter.Options.ConversionDelay = 3;

    By default the conversion starts as soon as the page has loaded. ConversionDelay is the extra time, in seconds, the converter waits after that before it renders, so 3 seconds covers the 1.5-second script with room to spare.

  6. 6Convert and save

    Program.cs · lines 23–25
    PdfDocument doc = converter.ConvertHtmlString(html);
    doc.Save("report.pdf");
    doc.Close();

    ConvertHtmlString renders the page with the delay and the PDF has the totals. Save writes report.pdf and Close releases the document.

  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;

// A report whose totals are written by a script 1.5 seconds after the page
// has loaded, the way a chart or a fetch() call fills in a page.
string html = """
    <h1>Quarterly report</h1>
    <p>Sales for July to September 2026.</p>
    <div id="totals">Loading totals...</div>
    <script>
      setTimeout(() => {
        document.getElementById("totals").innerHTML =
          "<h2>Totals</h2><p>Revenue: 1,284,500 EUR</p><p>Orders: 9,312</p>";
      }, 1500);
    </script>
    """;

HtmlToPdf converter = new HtmlToPdf();

// By default the page is captured as soon as it has loaded, so the PDF
// shows "Loading totals...". Wait 3 more seconds for the script to run.
converter.Options.ConversionDelay = 3;

PdfDocument doc = converter.ConvertHtmlString(html);
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.

Let the page start the conversion

When you cannot know how long the scripts take, set StartupMode to HtmlToPdfStartupMode.Manual and call selectpdf.start() from the page once it is ready. The converter exposes the selectpdf object only inside the conversion, so the typeof check keeps the page working in a browser. If the call never comes, the conversion fails after NavigationTimeout seconds.

StartFromJavaScript.cs
using SelectPdf.Universal;

static class StartFromJavaScript
{
    public static void Run()
    {
        // the page tells the converter when it is ready: no fixed delay to guess
        HtmlToPdf converter = new HtmlToPdf();
        converter.Options.StartupMode = HtmlToPdfStartupMode.Manual;
        converter.Options.NavigationTimeout = 30; // fails if selectpdf.start() is never called

        PdfDocument doc = converter.ConvertHtmlString("""
            <h1>Order summary</h1>
            <div id="items">Loading items...</div>
            <script>
              setTimeout(() => {
                document.getElementById("items").innerHTML =
                  "<p>3 x Laptop stand</p><p>1 x USB-C dock</p>";
                if (typeof selectpdf == "object") selectpdf.start();
              }, 2000);
            </script>
            """);
        doc.Save("order-summary.pdf");
        doc.Close();
    }
}

Render with the print styles

The payment terms are hidden on screen and shown by the @media print rules. The converter renders with the screen styles by default, so they are missing from the PDF; CssMediaType = HtmlToPdfCssMediaType.Print applies the print styles instead, and the online-payment line disappears.

PrintStyles.cs
using SelectPdf.Universal;

static class PrintStyles
{
    public static void Run()
    {
        // the terms are hidden on screen and shown only by the print styles
        HtmlToPdf converter = new HtmlToPdf();
        converter.Options.CssMediaType = HtmlToPdfCssMediaType.Print;

        PdfDocument doc = converter.ConvertHtmlString("""
            <style>
              .print-only { display: none; }
              @media print {
                .print-only { display: block; }
                .screen-only { display: none; }
              }
            </style>
            <h1>Invoice #1042</h1>
            <p class="screen-only">Pay online with the button below.</p>
            <p class="print-only">Payment terms: 30 days from the invoice date.</p>
            """);
        doc.Save("invoice-print.pdf");
        doc.Close();
    }
}

Keep wide content instead of cutting it

Six 300-pixel columns make the table wider than the page, which is laid out 1,024 pixels wide (WebPageWidth). By default content past the right edge is cut, as a browser cuts it when it prints, and the Total column is lost. FitWideContent = true lays the page out at the width it needs, up to four times WebPageWidth, and scales the whole page down to fit, so every column is in the PDF, smaller.

WideContent.cs
using SelectPdf.Universal;

static class WideContent
{
    public static void Run()
    {
        // six 300-pixel columns: wider than the 1,024-pixel web page, so by
        // default the Total column is cut, as a browser cuts it when it prints
        HtmlToPdf converter = new HtmlToPdf();
        converter.Options.FitWideContent = true; // lay out at full width, scale to fit

        PdfDocument doc = converter.ConvertHtmlString("""
            <style>th { min-width: 300px; }</style>
            <h1>Stock by warehouse</h1>
            <table border="1" style="border-collapse: collapse">
              <tr><th>Item</th><th>Berlin</th><th>Lyon</th>
                  <th>Milan</th><th>Vienna</th><th>Total</th></tr>
              <tr><td>Laptop stand</td><td>120</td><td>85</td>
                  <td>64</td><td>40</td><td>309</td></tr>
            </table>
            """);
        doc.Save("wide-table.pdf");
        doc.Close();
    }
}
§TROUBLESHOOTING

Common problems and fixes.

Web fonts are missing or the text renders incorrectly

Usually the fonts have not finished loading when the page is captured. Set ConversionDelay (for example 2) and make sure the font files are reachable from the page; WOFF2, WOFF, TTF and OTF fonts are embedded.

Lazy-loaded images or reveal-on-scroll content are missing

LazyImagesLoadingEnabled is on by default and waits LazyImagesLoadingDelay milliseconds per PDF page (40 by default). A short delay can leave out content that loads when it is scrolled into view; raise it. With ManagedContentTrimming set to false, also set WebPageHeight, for example 768.

The PDF shows the login page instead of the content

The converter makes its own request, so it is anonymous. For HTTP or Windows authentication set Options.Authentication.Username and Password; for cookie authentication send the signed-in user's cookie with Options.HttpCookies.Add(name, value).

Conversion error: Navigation timeout

The page took longer than NavigationTimeout (60 seconds by default): raise it, or set RenderPageOnTimeout = true to convert what has rendered. In manual startup mode the same error means selectpdf.start() was never called.

Wide content is cut off at the right edge

Content wider than WebPageWidth (1024 pixels by default) is cut at the right edge. Set FitWideContent = true to lay the page out at the width it needs and scale it down to fit, raise WebPageWidth, or let the page lay the content out narrower, for example with white-space: pre-wrap.

An image is split between two pages

Put break-inside: avoid on the image or on the element that contains it, and the image moves whole to the next PDF page.

§GOOD TO KNOW

Good to know.

  • ConversionDelay is additional time, in seconds, that the converter waits after the web page has loaded, for content that appears after the load event. The default is 0. Source
  • With StartupMode set to Manual, the conversion starts only when the page calls selectpdf.start(); if it is never called, the conversion times out. Source
  • ConvertHtmlString(String, String) takes a base URL: base URL + relative image or CSS URL = full absolute URL. Source
  • CssMediaType is Screen by default; set it to Print to render the page with its printer-specific styles. Source
  • The converter waits 60 seconds for a page by default; NavigationTimeout changes it, and RenderPageOnTimeout converts what has rendered instead of failing. Source
?FAQ

Questions, answered.

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

Usually the page was captured before its scripts finished. Set HtmlToPdfOptions.ConversionDelay to wait a few seconds after the page has loaded, or set StartupMode to Manual and call selectpdf.start() from the page when it is ready.
ConversionDelay waits a fixed number of seconds and needs no change to the page. Manual startup waits until the page calls selectpdf.start(), which suits pages whose loading time varies; NavigationTimeout is the upper limit.
Relative paths in an HTML string cannot be resolved on their own. Use ConvertHtmlString(html, baseUrl): the base URL plus each relative image, CSS or script path gives the full address.
Yes. Set Options.Authentication.Username and Password for HTTP authentication, or add the authentication cookie with Options.HttpCookies.Add for cookie (forms) authentication.