HTML to PDF in C#: .NET Libraries, Examples, and Tradeoffs

7 August 2025
This post thumbnail

To convert HTML to PDF in C#, use PuppeteerSharp or Playwright for modern CSS and JavaScript. Keep DinkToPdf only for existing simple templates, or use a managed API when you do not want to operate Chromium.

After testing some of the major open source libraries, I’ve put together this guide to hopefully save you some time. By the end, you should know which library fits your needs best and how to get it working.

tl;dr: PuppeteerSharp or Playwright for modern sites

If you need browser-faithful rendering of modern websites with complex CSS and JavaScript, PuppeteerSharp is a strong default. Playwright for .NET provides similar Chromium rendering with a maintained browser-install workflow; see the Playwright HTML-to-PDF guide for a complete example.

Use DinkToPdf only when you already have simple templates built around wkhtmltopdf and can accept its old CSS engine and native-library deployment. For ASP.NET Core and Razor views, the dedicated .NET Core HTML-to-PDF guide covers the application integration.

PuppeteerSharp

PuppeteerSharp is the .NET port of the popular Puppeteer library. It runs a headless Chrome or Firefox browser under the hood, and it can render anything that a browser can. Modern CSS (think flexbox and CSS grid), JavaScript frameworks etc. are supported and will all work as expected. The PuppeteerSharp HTML-to-PDF guide goes deeper into headers, footers, and deployment.

Here’s how to use it for HTML to PDF conversion. First, install it with the .NET CLI:

dotnet add package PuppeteerSharp

When you first run it, PuppeteerSharp will download a compatible version of Chromium automatically. If not, you can download Chromium with:

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

Here’s how to generate a PDF from a URL:

using PuppeteerSharp;

public async Task GeneratePdfFromUrl()
{
    await new BrowserFetcher().DownloadAsync();

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

    using var page = await browser.NewPageAsync();
    await page.GoToAsync("https://transformy.io/guides/");

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

You can also generate PDFs from HTML strings:

public async Task GeneratePdfFromHtml()
{
    await new BrowserFetcher().DownloadAsync();

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

    var html = @"
        <html>
        <head>
            <style>
                body { font-family: Arial, sans-serif; }
                .header { color: #333; border-bottom: 2px solid #ddd; }
            </style>
        </head>
        <body>
            <h1 class='header'>Invoice #2025-001</h1>
            <p>Total: $1,299.99</p>
        </body>
        </html>";

    await page.SetContentAsync(html);
    await page.PdfAsync("invoice.pdf", new PdfOptions
    {
        Format = PaperFormat.A4,
        MarginOptions = new MarginOptions
        {
            Top = "1in",
            Bottom = "1in",
            Left = "0.5in",
            Right = "0.5in"
        }
    });
}

Where PuppeteerSharp really shines is dealing with JavaScript-heavy content. Let’s say you want to create your PDF only once .charts-container has done loading:

// Wait for dynamic content to load
await page.WaitForSelectorAsync(".charts-container");
await page.PdfAsync("dashboard-report.pdf");

Puppeteer is powerful, but it comes at a cost. You’re running a browser for every conversion, which means higher memory use and slower generation times. A lot of parallel conversions are also more complicated to scale.

DinkToPdf

DinkToPdf is a .NET wrapper around wkhtmltopdf. Both wkhtmltopdf and its QtWebKit engine are no longer maintained, so DinkToPdf is a legacy compatibility choice rather than a default for a new application.

It can still fit an existing system with trusted, basic HTML and CSS. Don’t expect it to render newer CSS features like flexbox or CSS grid.

Before you can use DinkToPdf, you need the native wkhtmltopdf library for the operating system and architecture where the application runs. The wkhtmltopdf installation and migration guide covers the underlying binary in more detail.

dotnet add package DinkToPdf --version 1.0.8

DinkToPdf.Native NuGet and native DLL errors

NuGet cannot find DinkToPdf.Native, DinkToPdf.Native.Windows, or DinkToPdf.Native.Linux because those are not current package IDs in the official feed. Install DinkToPdf, then supply a compatible libwkhtmltox native library for your deployment target.

If you get DllNotFoundException: Unable to load DLL 'libwkhtmltox', check that:

  • The native library matches the process architecture (x64 or x86).
  • libwkhtmltox.dll, libwkhtmltox.so, or libwkhtmltox.dylib is copied to the published output or another loadable path.
  • Linux has the native dependencies required by that wkhtmltopdf build.
  • Your container or cloud runtime uses the same operating system and architecture you built for.

You’ll also need to configure dependency injection in your startup:

// In Startup.cs or Program.cs
services.AddSingleton(typeof(IConverter), new SynchronizedConverter(new PdfTools()));

Here’s how to generate a PDF from HTML:

using DinkToPdf;
using DinkToPdf.Contracts;

public class PdfService
{
    private readonly IConverter _converter;

    public PdfService(IConverter converter)
    {
        _converter = converter;
    }

    public byte[] GeneratePdfFromHtml(string html)
    {
        var doc = new HtmlToPdfDocument()
        {
            GlobalSettings = {
                ColorMode = ColorMode.Color,
                Orientation = Orientation.Portrait,
                PaperSize = PaperKind.A4,
            },
            Objects = {
                new ObjectSettings() {
                    PagesCount = true,
                    HtmlContent = html,
                    WebSettings = { DefaultEncoding = "utf-8" }
                }
            }
        };

        return _converter.Convert(doc);
    }
}

You can also convert from URLs:

public byte[] GeneratePdfFromUrl(string url)
{
    var doc = new HtmlToPdfDocument()
    {
        GlobalSettings = {
            ColorMode = ColorMode.Color,
            Orientation = Orientation.Portrait,
            PaperSize = PaperKind.A4,
            Margins = new MarginSettings() { Top = 10 }
        },
        Objects = {
            new ObjectSettings() {
                Page = url,
                LoadSettings = new LoadSettings()
                {
                    BlockLocalFileAccess = false
                }
            }
        }
    };

    return _converter.Convert(doc);
}

DinkToPdf can be fast for simple templates, but it uses an old rendering engine. You might need to simplify your HTML and CSS, and you must ship the correct native library in every cloud or container environment.

Weasyprint.Wrapped

Weasyprint.Wrapped brings Python’s excellent WeasyPrint library to the .NET world. It’s particularly good if you need precise control over CSS styling and don’t mind working with a Python dependency.

Installing requires both the NuGet package and Python with WeasyPrint:

Install-Package Weasyprint.Wrapped

You’ll also need Python and WeasyPrint installed on your system:

pip install weasyprint

Here’s how to use it:

using Weasyprint.Wrapped;

public class WeasyPrintService
{
    public void GeneratePdfFromHtml(string html, string outputPath)
    {
        var weasyprint = new WeasyPrintWrapper();

        weasyprint.GeneratePdf(
            html: html,
            outputPath: outputPath,
            options: new WeasyPrintOptions
            {
                PaperSize = "A4",
                Margin = "1cm"
            }
        );
    }

    public void GeneratePdfFromUrl(string url, string outputPath)
    {
        var weasyprint = new WeasyPrintWrapper();
        weasyprint.GeneratePdfFromUrl(url, outputPath);
    }
}

You can also work with custom CSS:

var html = @"
    <html>
    <body>
        <h1>Custom Styled Document</h1>
        <p>This will have custom styling applied.</p>
    </body>
    </html>";

var css = @"
    @page { size: A4; margin: 2cm; }
    body { font-family: Georgia, serif; }
    h1 { color: #2c3e50; }";

weasyprint.GeneratePdf(html, "styled-document.pdf", css: css);

Weasyprint.Wrapped produces high-quality PDFs with excellent CSS support, but it’s slower than other options and requires managing a Python dependency. It’s great for documents where visual fidelity is crucial.

iText 7 pdfHTML

iText 7 pdfHTML is part of the commercial iText suite, though it offers a free AGPL version. The free version requires your application to be open source under AGPL. If you’re building proprietary software, you’ll need a commercial license which can be expensive.

Here’s how to install the NuGet package:

Install-Package itext7.pdfhtml

Converting HTML to PDF is straightforward:

using iText.Html2pdf;
using iText.Kernel.Pdf;
using iText.Layout;

public class ITextService
{
    public void GeneratePdfFromHtml(string html, string outputPath)
    {
        using var writer = new PdfWriter(outputPath);
        using var pdf = new PdfDocument(writer);

        HtmlConverter.ConvertToPdf(html, pdf);
    }

    public void GeneratePdfFromUrl(string url, string outputPath)
    {
        using var writer = new PdfWriter(outputPath);
        using var pdf = new PdfDocument(writer);

        HtmlConverter.ConvertToPdf(new Uri(url), pdf);
    }
}

You can also customize the conversion with properties:

public void GenerateCustomPdf(string html, string outputPath)
{
    var properties = new ConverterProperties();
    properties.SetBaseUri("https://transformy.io/"); // For resolving relative URLs

    using var writer = new PdfWriter(outputPath);
    using var pdf = new PdfDocument(writer);

    HtmlConverter.ConvertToPdf(html, pdf, properties);
}

iText produces good PDFs and has excellent documentation, but the licensing situation can be a dealbreaker. Make sure you understand the AGPL requirements or budget for a commercial license. For other .NET-native approaches, compare the dedicated iTextSharp, Aspose.HTML, and PDFSharp guides.

Conclusion

Here’s my take on each library after using them in real projects:

Option CSS and JavaScript fidelity Licensing Deployment and maintenance Production scale
PuppeteerSharp High; browser-faithful MIT You install and operate Chromium Requires browser pooling and resource controls
Playwright for .NET High; browser-faithful Apache-2.0 You install and operate Chromium Requires browser pooling and resource controls
DinkToPdf Low for modern CSS; limited JavaScript LGPL, plus wkhtmltopdf You ship legacy native libraries Fast for simple templates, but carries maintenance risk
Weasyprint.Wrapped Strong print CSS; no JavaScript BSD You also operate Python and WeasyPrint Better for controlled documents than dynamic pages
iText 7 pdfHTML Document-oriented; limited browser behavior AGPL or commercial .NET library with licensing review Suitable when its HTML/CSS subset fits
Managed Chrome API High; browser-faithful Commercial service No browser infrastructure to operate Designed for concurrent production rendering

For a new self-hosted implementation, start with PuppeteerSharp or Playwright. Keep DinkToPdf only when an existing simple template already depends on it. If browser deployment and scaling are the problem you want to avoid, use a managed HTML-to-PDF API.

The licensing considerations for iText are important. If you need a permissive open-source license, PuppeteerSharp’s MIT license and Playwright’s Apache-2.0 license give you more freedom.