Generate PDF files in your application

Create invoices and reports from HTML with the PdfPecker library - no Chrome, no Puppeteer, works on the Free plan.

Invoices, reports, tickets, contracts — sooner or later most applications need to produce a PDF. The usual advice on the internet is to render HTML with a headless browser through Puppeteer, Playwright or Selenium. On shared hosting that is the worst option: it starts a separate Chrome process on the server, which is not allowed on the Free plan and on Premium needs an exception from our security system, and it eats hundreds of megabytes of memory from your website limit.

We recommend PdfPecker instead: an open-source .NET library that converts HTML (and Markdown) to PDF inside your application, without any browser. It works on the Free and Premium plans alike, with no exception needed. Our second recommended library of the same kind is PeachPDF.

Why PdfPecker

PdfPecker Headless Chrome (Puppeteer, Playwright)
How it works Pure managed .NET DLL (about 6 MB) loaded into your application Downloads and starts a separate Chrome browser (about 600 MB)
Free plan Works Not allowed — the website is suspended by the security system
Premium plan Works Allowed after an exception requested through a support ticket
Dependencies None beyond the .NET runtime; no native binaries Chrome binaries, their updates, a browser pool to manage
Memory About 90 MB peak under load (vendor benchmark) 800 MB to 1 GB — usually more than the memory limit of a website
Speed About 1 ms for a one-page invoice (vendor benchmark) About 100 ms plus browser start-up
Deployment One NuGet package, nothing to install on the server Browser binaries downloaded into the website folder at start-up, hundreds of MB of disk space

Other reasons it is a good fit for hosted applications:

  • Modern CSS. Flexbox, grid, multi-column layout, custom properties, @page rules with running headers and footers, page numbers and page-break control. Your existing HTML templates usually convert without changes.
  • Fonts and languages. TrueType, OpenType, WOFF and WOFF2 fonts with embedding and subsetting, right-to-left scripts, hyphenation.
  • Standards. PDF/A-2, 3 and 4 for archiving, PDF/UA accessibility, AES-256 encryption, PAdES digital signatures, Factur-X / ZUGFeRD / XRechnung e-invoices.
  • More than conversion. Markdown to PDF, merging and splitting, stamping and watermarks, form filling, PDF to PNG/JPEG/WebP, PDF to HTML, and a fluent C# API in the style of QuestPDF.
  • Safe with untrusted input. No JavaScript, no network access by default, a page limit and cancellation.
  • Thread-safe. The conversion methods are static; many requests can generate documents at the same time.
  • MIT license, source on GitHub, targets .NET 8, 9 and 10 on Windows and Linux.

Note

PdfPecker is a document engine, not a web browser. It does not run JavaScript, so a page that builds its content with scripts in the browser must be rendered on the server first (a Razor view, a template engine, a string builder). Converting a live website or taking screenshots still needs Chrome and the Premium plan.

Install the package

dotnet add package PdfPecker

Or in Visual Studio: Manage NuGet Packages → search for PdfPecker → Install. No further set-up is needed on your computer or on the server.

A simple test

  1. Create a project

    Create an ASP.NET Core Empty project (.NET 8 or newer) and install the PdfPecker package.

  2. Replace Program.cs

    The application converts a small invoice to PDF and returns it on /invoice:

    using PdfPecker;
    
    var builder = WebApplication.CreateBuilder(args);
    var app = builder.Build();
    
    string html = """
    <!DOCTYPE html>
    <html>
    <head>
      <style>
        body { font-family: Arial, sans-serif; }
        h1 { color: #2563eb; }
        table { width: 100%; border-collapse: collapse; margin-top: 16px; }
        th, td { border: 1px solid #ccc; padding: 6px 8px; text-align: left; }
        th { background: #f1f5f9; }
        td.num { text-align: right; }
      </style>
    </head>
    <body>
      <h1>Invoice 2026-104</h1>
      <p>Billed to: Example Ltd., Prague</p>
      <table>
        <tr><th>Item</th><th>Qty</th><th>Price</th></tr>
        <tr><td>Premium hosting</td><td class="num">1</td><td class="num">120.00</td></tr>
        <tr><td>Extra storage</td><td class="num">2</td><td class="num">10.00</td></tr>
      </table>
    </body>
    </html>
    """;
    
    var options = new PdfOptions
    {
        PageSize = PageSize.A4,
        Margins = PageMargins.FromMillimeters(20, 15, 20, 15),
        FooterText = "Page {page} of {pages}",
        Title = "Invoice 2026-104",
    };
    
    app.MapGet("/invoice", () =>
        Results.File(HtmlToPdf.Convert(html, options), "application/pdf", "invoice.pdf"));
    
    app.Run();
    
  3. Publish and open

    Publish the project to your website — see Deploy from Visual Studio — and open https://yourdomain.com/invoice. The browser downloads a one-page PDF with the invoice table and Page 1 of 1 in the footer.

In an MVC controller the last step is the same: render the view to a string and return File(HtmlToPdf.Convert(html), "application/pdf", "invoice.pdf"). To save the document instead of sending it, call File.WriteAllBytes("invoice.pdf", pdf) or pass a stream to HtmlToPdf.Convert(html, stream). For long documents use await HtmlToPdf.ConvertAsync(html) so the request thread is not blocked.

Page size, margins, headers and footers

Everything can be set in code through PdfOptions, or directly in CSS with an @page rule — both apply, CSS wins:

@page {
  size: A4 landscape;
  margin: 20mm 15mm;
  @bottom-right { content: "Page " counter(page) " of " counter(pages) }
}
h2 { page-break-before: always; }

Images, stylesheets and fonts

PdfPecker never downloads anything by itself. Resources come from one of three places:

  • Files on the server. Set BaseDirectory and reference images and stylesheets with relative paths. Only files inside that folder are read:

    var options = new PdfOptions { BaseDirectory = Path.Combine(app.Environment.WebRootPath, "templates") };
    
  • Data URIs. <img src="data:image/png;base64,..."> works in <img> tags and in CSS backgrounds.

  • Remote URLs. Allowed only through a ResourceResolver that fetches the URLs you decide to trust:

    var http = new HttpClient();
    var options = new PdfOptions
    {
        ResourceResolver = url => url.StartsWith("https://cdn.example.com/")
            ? http.GetByteArrayAsync(url).GetAwaiter().GetResult()
            : null,
    };
    

Fonts are not taken from the server's Windows fonts folder. Without registered fonts the built-in fallbacks cover Western, Central European, Greek and Cyrillic text; for anything else (CJK, emoji, a corporate font) ship the font file with the application and register it — only the glyphs you use are embedded:

options.Fonts.AddFile(Path.Combine(app.Environment.WebRootPath, "fonts", "OpenSans-Regular.ttf"));

Limitations

  • No JavaScript, <canvas>, <iframe>, <video> or <audio>.
  • No AVIF images; use PNG, JPEG, WebP or SVG.
  • A few CSS features are missing or differ from Chrome; the vendor keeps an honest list on the Limitations and CSS support pages.
  • The package targets .NET 8 and newer. For an application on .NET Framework 4.x use a managed library such as PdfSharp or QuestPDF, or move the PDF generation into a small .NET 8+ website.

If you are migrating from PuppeteerSharp or Playwright, the vendor has a side-by-side guide that maps Chrome's page.pdf() options to PdfOptions.

Still stuck? Our support team is happy to help.
Ask the community Open a support ticket