ScreenshotNeo

BlogHTML to image & PDF

How to Generate PDFs from HTML with Puppeteer-Sharp in C#

Render a URL or HTML as a PDF with Puppeteer-Sharp in C#. Learn browser setup, font and content waits, print options, deployment caveats, and fixes for common errors.

By the ScreenshotNeo team4 October 20268 min read

Puppeteer-Sharp generates PDFs by controlling headless Chrome or Chromium from .NET. Install the package, download a compatible browser, load a URL or HTML, wait for the page content and fonts to be ready, then call PdfAsync. PDF output uses print CSS by default and is documented as supported only in Chrome headless.

This guide shows both URL and supplied-HTML workflows, the main print settings, readiness checks, deployment considerations, and common fixes. Confirm the API symbols against the PuppeteerSharp version and target framework in your project; package compatibility changes over time. See the Puppeteer-Sharp project and IPage API reference.

1. Install Puppeteer-Sharp

Add PuppeteerSharp to the project:

dotnet add package PuppeteerSharp

Check the current NuGet listing and the project’s target framework before choosing or pinning a version. Framework support differs by package flavor and evolves; test the exact framework, browser build, operating system, and deployment image you intend to use. The project describes Windows, macOS, and Linux support, and its package instructions call out an X-server requirement on Linux.

2. Generate a PDF from HTML

This minimal console example downloads a compatible browser, launches headless Chrome, sets HTML, waits for fonts, and writes an A4 PDF. Add an application-specific readiness check when the markup fills in asynchronously.

using PuppeteerSharp;

var html = """
<!doctype html>
<html>
<head>
  <meta charset=\"utf-8\">
  <style>
    @page { size: A4; margin: 18mm; }
    body { font: 12pt Arial, sans-serif; color: #222; }
    h1 { color: #174ea6; }
  </style>
</head>
<body>
  <h1>Monthly report</h1>
  <p>This document was rendered from HTML.</p>
</body>
</html>
""";

var fetcher = new BrowserFetcher();
await fetcher.DownloadAsync();

await using var browser = await Puppeteer.LaunchAsync(
    new LaunchOptions { Headless = true });
await using var page = await browser.NewPageAsync();

await page.SetContentAsync(html);
await page.EvaluateExpressionHandleAsync("document.fonts.ready");

await page.PdfAsync("output.pdf", new PdfOptions
{
    Format = PaperFormat.A4,
    PrintBackground = true
});

The browser-fetch and font-wait pattern follows the project’s documented examples. If you need bytes or a stream rather than a file, use the documented PdfDataAsync or PdfStreamAsync API for the installed version. Dispose browser and page resources when done; the await using form scopes their lifetimes.

3. Generate a PDF from a URL

For an existing page, navigate before printing. Wait for a signal that means the page’s actual content is ready. The project examples show selector waits; its navigation example also discusses network-idle waiting for remotely loaded assets.

using PuppeteerSharp;

var fetcher = new BrowserFetcher();
await fetcher.DownloadAsync();

await using var browser = await Puppeteer.LaunchAsync(
    new LaunchOptions { Headless = true });
await using var page = await browser.NewPageAsync();

await page.GoToAsync("https://example.com");
await page.WaitForSelectorAsync("main h1");
await page.EvaluateExpressionHandleAsync("document.fonts.ready");

await page.PdfAsync("page.pdf", new PdfOptions
{
    Format = PaperFormat.A4,
    PrintBackground = true
});

Choose a selector that appears only after the content you need is rendered. A generic network-idle condition can be unsuitable for pages with long polling or persistent connections; prefer a page-specific selector or application signal when available. Account for remote images and fonts if they are part of the document.

4. Wait for dynamic content, images, and fonts

A PDF captures the rendered page at a moment in time. If JavaScript is still filling in the document, fonts are unresolved, or assets have not loaded, output can be incomplete. A robust sequence is:

  1. Navigate to the URL or set the HTML.
  2. Wait for a meaningful selector or application-specific condition that confirms dynamic content is present.
  3. Wait for document.fonts.ready when typography matters. The official example warns that skipping font readiness can leave text absent in the PDF.
  4. Ensure required remote assets have had a chance to load. Use network-idle navigation waiting only when it makes sense for that page.
  5. Generate the PDF.

For generated HTML, SetContentAsync alone does not guarantee that your application’s later asynchronous work is complete. Add an explicit wait that matches the page’s own rendering process.

5. Configure print layout and PDF options

PdfOptions controls the page geometry and printed content. The API reference documents these settings and their defaults; check the installed package’s API when adopting a snippet.

Setting Use Documented behavior
Format Choose a standard paper size, such as A4. When set, takes priority over explicit width and height.
Width, Height Set custom page dimensions when a named paper format is not suitable. Do not expect these dimensions to override Format when both are set.
Landscape Print wide tables or layouts in landscape orientation. Disabled by default.
MarginOptions Set top, bottom, left, and right margins. Margins default to none in the documented options.
PrintBackground Include CSS backgrounds and background images. Disabled by default.
PreferCSSPageSize Give CSS @page size rules priority. Not preferred by default.
PageRanges Restrict output to selected pages. Use the range syntax accepted by the installed API/browser.
Header/footer options Add repeating header or footer templates. Disabled by default. Templates can use classes such as date, title, url, pageNumber, and totalPages.
Outline option Request a PDF outline. The API describes outline generation as also tagging the PDF for accessibility, with a qualification that it currently works only in old headless mode. Verify the deployed browser and package behavior before relying on it.

Example with explicit margins and landscape output:

await page.PdfAsync("wide-report.pdf", new PdfOptions
{
    Format = PaperFormat.A4,
    Landscape = true,
    PrintBackground = true,
    MarginOptions = new MarginOptions
    {
        Top = "12mm",
        Bottom = "14mm",
        Left = "10mm",
        Right = "10mm"
    }
});

PDF generation uses print CSS media by default. If the page is designed for screen media and you specifically want those styles, call EmulateMediaTypeAsync(MediaType.Screen) before printing. If the document should follow print styles, keep the default and author or adjust its print CSS.

6. Choose the right output form

Use PdfAsync(path, options) when the application should write a file. The API also documents PdfDataAsync for PDF bytes and PdfStreamAsync for stream-oriented handling. These alternatives are useful when another part of the application stores, returns, or processes the PDF without first saving a local file. Check the overloads available in your installed version.

7. Deployment, performance, and reliability

  • Browser availability: The documented BrowserFetcher flow downloads a browser build. In managed deployments, decide how browser binaries are provisioned and kept compatible with the package.
  • Runtime dependencies: Validate Chrome/Chromium launch in the actual host. Linux requirements depend on the host image; the project instructions specifically call out an X-server requirement. Do not assume a generic framework or OS support statement guarantees a particular container or serverless setup.
  • Resource lifetime: Scope browser and page objects and dispose them after generation. For repeated jobs, choose a lifecycle that avoids unnecessary browser launches while still cleaning up resources; measure behavior in your own workload.
  • Waits: Waiting for every network request can add latency or never finish on pages with persistent traffic. A narrow selector or application-level readiness condition can provide a more relevant signal.
  • Fonts and assets: Remote fonts and images add network dependencies and can delay or alter output. Make readiness explicit and validate the rendered document in the deployment environment.
  • Cost: PuppeteerSharp is installed through NuGet and uses a browser runtime. The cited sources do not establish a universal rendering cost or benchmark; account for the compute, memory, browser storage, and operational work in your own hosting environment.
  • Version compatibility: Package targets, browser behavior, and headless modes evolve. Pin and review package versions according to your release process, and validate output after upgrades.

The API documents PDF output as currently supported only in Chrome headless. Treat that as a runtime constraint when selecting the browser and deployment approach.

8. Troubleshooting

Symptom Likely cause What to do
Text is missing or uses an unexpected font Font loading had not completed when printing began. Wait for document.fonts.ready; account for remote font requests and confirm they can load from the host.
PDF is blank or missing dynamic sections Client-side rendering had not finished. Wait for a meaningful selector or application-specific condition before calling PdfAsync.
PDF styling differs from the browser view PDF generation defaults to print CSS media. Adjust print styles, or call EmulateMediaTypeAsync(MediaType.Screen) when screen styling is intended.
Background colors or images are missing PrintBackground defaults to false. Set PrintBackground = true when backgrounds are required.
Navigation or readiness wait appears stuck A page may keep connections active, or the selected readiness condition may never occur. Use a condition tied to the content you need; avoid relying on broad network-idle waits for pages with persistent connections.
Browser fails to launch in Linux The browser build or host runtime dependencies may not match the environment; project instructions call out X-server requirements. Check the PuppeteerSharp Linux troubleshooting guidance, browser provisioning, and dependencies in the exact deployment image.
PDF call is unsupported in the chosen browser mode The documented PDF workflow is Chrome-headless-only. Use a compatible Chrome headless runtime and verify behavior for the package/browser versions deployed.
Custom page size seems ignored Format takes priority when it is set alongside dimensions. Use the intended format or dimensions consistently; inspect CSS @page behavior and the PreferCSSPageSize setting.

9. Or skip the browser setup

If your requirement is a screenshot of a URL rather than a paginated PDF from custom HTML, ScreenshotNeo provides a one-call website screenshot API. Puppeteer-Sharp remains the fit here when you need to generate a PDF from supplied markup and control print layout.

Install the optional HTTP client in Python with pip install requests if using that example. See the ScreenshotNeo API documentation for request options.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests

r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
  • Cookie banners, newsletter popups, and chat widgets are removed before the shot; each cleanup step can be turned off.
  • Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed; response headers identify the page verdict and billing status.
  • An MCP server lets AI agents use take_screenshot, get_page_info, and capture_pdf.
  • The free plan includes 1,000 screenshots a month with no card. Paid plans start at $5 for 3,000 screenshots; every feature is on every plan.

Sign up for 1,000 free screenshots a month, no card required.

10. FAQ

Can Puppeteer-Sharp create a PDF from a string of HTML?

Yes. Call SetContentAsync with the markup, wait for any asynchronous content and fonts that matter, then call a PDF output method.

Does PDF output use screen styles?

Print CSS media is the default. Emulate screen media before printing only when the screen stylesheet is the desired output.

Can I return a PDF without writing a file?

The API documents byte and stream output methods, PdfDataAsync and PdfStreamAsync. Check their available overloads in your installed package.

Will the same setup work in every container?

Not automatically. Validate the browser build, runtime dependencies, framework, and host configuration in the exact environment where the application will run.