Best Open-Source HTML-to-PDF Libraries for C#
Compare open-source C# HTML-to-PDF libraries, browser engines, licensing, deployment trade-offs, runnable examples, and a practical selection checklist.
Short answer: For HTML that depends on JavaScript, modern CSS, web fonts, lazy loading, or browser behavior, start with PuppeteerSharp or Playwright for .NET. They drive real browser engines and usually provide the closest match to what users see. DinkToPdf is a wrapper around wkhtmltopdf and can still fit an existing legacy integration, but its older Qt WebKit engine requires careful compatibility, maintenance, and security review. HTML Renderer is a managed C# option to investigate for simpler documents. QuestPDF is an adjacent choice when you can author the document in C# instead of converting existing HTML.
There is no universal winner. Choose by source format, JavaScript requirements, CSS fidelity, browser installation, deployment footprint, licensing, maintenance, and security requirements. Verify current releases, license files, supported .NET targets, browser installation instructions, and PDF API behavior before committing.
Which library should you choose?
| Requirement | Start with | Verify before adoption |
|---|---|---|
| Existing HTML with JavaScript or modern browser layout | PuppeteerSharp or Playwright for .NET | Browser install and update flow, fonts, print CSS, headers and footers, page breaks, resource loading, concurrency, containers, and browser security |
| Existing Playwright test infrastructure or multiple browser engines | Playwright for .NET | Current PDF API behavior and which engine supports your output path |
| Existing wkhtmltopdf integration | DinkToPdf | Native binaries, target platforms, current maintenance and security status, and whether your HTML works in the legacy renderer |
| Simple HTML with limited scripting | HTML Renderer | CSS support, JavaScript needs, pagination, fonts, and maintenance |
| New fixed-layout documents that do not need HTML templates | QuestPDF | Whether rewriting templates is acceptable and current licensing conditions |
For a new project with browser-like requirements, evaluate PuppeteerSharp and Playwright against the same representative documents. For an existing wkhtmltopdf deployment, test whether migration is worth the behavior change before replacing it.
What to evaluate before selecting a library
1. Source and rendering model
Decide whether HTML is an input contract. If templates already come from a CMS, frontend team, or email system, a browser renderer preserves that investment. If the document is a new invoice, report, or form with fixed structure, a C# layout library can avoid HTML conversion entirely.
2. JavaScript and asynchronous content
Ask whether content appears only after JavaScript runs. Browser-driven libraries can wait for a selector, a delay, fonts, or network activity. A managed HTML renderer may not execute the same scripts, and a legacy WebKit engine may behave differently from current Chromium.
3. CSS and pagination
Test print styles, flexbox and grid, web fonts, generated content, long tables, repeating headers, page breaks, fixed elements, SVG, images, and right-to-left text. Do not infer parity from a library name; render your own production fixtures.
4. Deployment and operations
Browser libraries require browser binaries and operating-system dependencies. Plan image size, startup time, sandboxing, font packages, browser updates, process limits, and concurrency. Native wrappers require the correct binary for every target platform and architecture.
5. Licensing
PuppeteerSharp’s repository identifies the project as MIT licensed: PuppeteerSharp repository. The wkhtmltopdf project identifies its utility as LGPLv3 and based on Qt WebKit. A wrapper’s license does not settle the licenses of its native engine or other dependencies. Check the current Playwright repository, browser distribution terms, wrapper license, and transitive dependencies for the exact versions you ship. This is an engineering checklist, not legal advice.
PuppeteerSharp
PuppeteerSharp describes itself as a .NET port of the Puppeteer API. It is a direct route when your team already knows Puppeteer concepts or wants a browser-based PDF workflow from C#. The project repository identifies an MIT license.
Strengths
- Uses a real browser engine for HTML, CSS, fonts, images, and JavaScript.
- Maps naturally to the Puppeteer mental model: launch, navigate, wait, and print.
- Suitable for pages whose content is assembled asynchronously.
Costs and risks
- Browser download and installation become part of deployment.
- Containers need compatible libraries, fonts, and a deliberate sandbox configuration.
- Do not assume the .NET port has identical API coverage, release timing, or behavior to upstream Puppeteer.
Illustrative C# workflow
using PuppeteerSharp;
await new BrowserFetcher().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", WaitUntilNavigation.Networkidle0);
await page.EvaluateExpressionAsync("document.fonts.ready");
await page.PdfAsync("output.pdf", new PdfOptions
{
Format = PaperFormat.A4,
PrintBackground = true,
PreferCSSPageSize = true
});
Check the current README and API reference before copying signatures into production. The exact browser download, launch, navigation, and PDF option names can change with package versions.
Playwright for .NET
The official Playwright .NET documentation lists Chromium, WebKit, and Firefox support and explains that browser dependencies must be installed. Playwright was created for end-to-end testing, but its browser automation building blocks can also be used manually for rendering workflows.
When it fits
- Your organization already uses Playwright for tests.
- You need to compare behavior across Chromium, WebKit, and Firefox during evaluation.
- You want locator and waiting primitives for content that appears asynchronously.
Deployment checklist
- Install the package and the required browser binaries in the build or image process.
- Install operating-system dependencies and all fonts used by your documents.
- Define a browser version policy and rebuild images when updating it.
- Limit concurrent pages and monitor memory and process counts.
- Review the current PDF or print API documentation for your package version before relying on a signature.
Illustrative C# workflow
using Microsoft.Playwright;
using var playwright = await Playwright.CreateAsync();
await using var browser = await playwright.Chromium.LaunchAsync(new BrowserTypeLaunchOptions
{
Headless = true
});
var page = await browser.NewPageAsync(new BrowserNewPageOptions
{
ViewportSize = new ViewportSize { Width = 1440, Height = 900 }
});
await page.GotoAsync("https://example.com", new PageGotoOptions
{
WaitUntil = WaitUntilState.NetworkIdle
});
await page.EvaluateAsync("() => document.fonts.ready");
// Confirm the current Playwright .NET PDF API for your installed version.
await page.PdfAsync(new PagePdfOptions
{
Path = "output.pdf",
Format = "A4",
PrintBackground = true,
PreferCSSPageSize = true
});
The PDF API and supported output behavior are version-sensitive. Verify the current official documentation and test the engine you intend to deploy.
DinkToPdf and wkhtmltopdf
DinkToPdf is a C#/.NET Core wrapper around wkhtmltopdf. The wkhtmltopdf project describes its command-line utility as open source under LGPLv3 and based on Qt WebKit.
This architecture matters: DinkToPdf does not replace the underlying renderer. You still deploy native engine binaries and inherit the rendering behavior and constraints of an older WebKit-based stack. Existing systems may depend on its output, but new deployments should review current maintenance, security posture, platform compatibility, and modern CSS and JavaScript requirements.
Use it when
- You already have a stable wkhtmltopdf-based production pipeline.
- Your templates are simple and have been validated against that exact binary.
- Migration risk is higher than the benefit of a browser engine.
Investigate before choosing it for new work
- Modern CSS layout, web fonts, complex SVG, and JavaScript timing.
- Native binary packaging for Linux, Windows, or containers.
- Security updates and maintenance of both wrapper and engine.
- Whether headers, footers, page breaks, and long tables match your requirements.
HTML Renderer
The HTML Renderer repository describes a cross-framework managed C# HTML renderer with PDF generation capabilities. That makes it a candidate for simpler documents where a browser is unnecessary.
The available evidence does not establish modern CSS or JavaScript parity. Treat it as an option to prove against representative documents. Include scripts, external assets, custom fonts, pagination, images, and long tables in the proof of concept. If the page depends on client-side rendering, assume you need a browser-based alternative until testing shows otherwise.
QuestPDF as an adjacent alternative
QuestPDF belongs in a different decision branch. It is useful when you can define the document in fluent C# rather than preserve HTML templates. It is not an HTML-to-PDF converter in the strict sense.
Choose this path when the document is a new fixed layout and your team accepts rewriting the template. Confirm current licensing conditions on the official project site before shipping.
Build a reliable browser-based conversion pipeline
- Pin the package and browser versions. Record the .NET SDK, library package, browser revision, base image, and font packages.
- Load the page with an explicit navigation policy. Set a timeout and choose a wait condition that matches the page. Network idle is useful but can be delayed by analytics or long polling.
- Wait for content that matters. Wait for a selector, a known application state, fonts, images, or a bounded delay. Avoid an unbounded sleep.
- Set print behavior explicitly. Enable background graphics when required, choose paper size or CSS page size, set margins, and define headers and footers deliberately.
- Close pages and browsers deterministically. Use async disposal and ensure failures cannot leave orphaned browser processes.
- Validate the resulting PDF. Check page count, file size, text presence, expected images, fonts, and page breaks with representative fixtures.
Useful HTML and CSS controls
@page {
size: A4;
margin: 18mm 14mm 20mm;
}
html, body {
-webkit-print-color-adjust: exact;
print-color-adjust: exact;
}
.invoice-table {
break-inside: avoid;
}
.invoice-table thead {
display: table-header-group;
}
.page-break {
break-before: page;
}
Browser support for print properties can vary. Keep a fixture that exercises each rule and inspect output after browser upgrades.
Common errors and fixes
| Symptom | Likely cause | Fix |
|---|---|---|
| Browser executable not found | Browser binaries were not downloaded in the build or runtime image | Install the required browser during image creation and verify the executable path at startup |
| Launch fails in a container | Missing system libraries, fonts, permissions, or an incompatible sandbox setup | Install documented dependencies, add fonts, run as a suitable user, and apply the library’s current container guidance |
| PDF is blank | Navigation failed, content is client-rendered, or capture ran before rendering completed | Record navigation errors, wait for a meaningful selector or application state, and verify the response status |
| Missing web fonts | Font requests failed, cross-origin access was blocked, or capture occurred too early | Check network logs, make fonts reachable, wait for document.fonts.ready, and install fallback fonts |
| Images are absent | Lazy loading, relative URLs, blocked requests, or a premature capture | Scroll or trigger lazy loading, use absolute asset URLs, wait for image completion, and inspect failed requests |
| Styles differ from the browser | Print media rules, missing CSS, browser version differences, or print-color settings | Use print emulation intentionally, load all stylesheets, set color adjustment, and pin the browser version |
| Page breaks split rows | Unsupported or conflicting break rules and table layout | Use print-specific table markup, test break-inside, and redesign rows that cannot split |
| Navigation never finishes | Analytics, WebSockets, polling, or third-party requests keep the network busy | Wait for a page-specific selector or bounded readiness signal instead of global network idle |
| High memory usage | A browser is launched per request or too many pages run concurrently | Reuse a controlled browser process, cap concurrency, recycle unhealthy workers, and measure on your workload |
| Different output after an upgrade | Browser, font, package, or base image changed | Pin versions, keep golden PDFs or structural assertions, and review diffs before rollout |
Performance, reliability, and cost considerations
Performance
- Launching a browser is usually more expensive than opening a page in an existing process. Measure cold and warm paths separately.
- Reuse a browser where safe, but isolate tenants and cap pages to prevent memory contention.
- Reduce third-party requests and avoid waiting for resources that do not affect the PDF.
- Cache immutable assets such as CSS, fonts, and logos inside your network or build image.
- Set bounded navigation and rendering timeouts and return actionable diagnostics.
Reliability
- Use a queue for large jobs and apply backpressure when browser workers reach their page limit.
- Retry transient navigation failures with a small limit; do not blindly retry deterministic HTML or authentication errors.
- Capture the URL, package version, browser version, viewport, locale, timezone, and readiness condition with each job.
- Protect the renderer from untrusted URLs with network egress controls, request allowlists, and resource limits.
Cost
Self-hosting shifts cost into compute, browser images, font packages, operations, and maintenance. A fair comparison requires your own workload: document size, JavaScript execution time, concurrency, cold starts, and retry rate. The dossier contains no reproducible benchmark, so do not treat one library as faster without testing on your target environment.
Or skip the browser setup
If your goal is a clean image or PDF of a web page rather than maintaining a rendering fleet, ScreenshotNeo provides a website screenshot API and MCP server. It accepts one GET request and returns a PNG, JPEG, WebP, or PDF. Its capture options include full-page shots with lazy images loaded, CSS-element capture, device presets or custom viewports, retina scale, PDF paper size and margins, custom CSS and JavaScript, click and wait actions, request blocking, headers, cookies, user agents, authorization, timezone, geolocation, caching, signed links, async jobs, bulk capture, and a usage API. See the ScreenshotNeo API documentation for current parameter details.
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 and consent banners are accepted and more than 60 known consent platforms, newsletter popups, and chat widgets are removed before the shot; each step can be turned off. Bot checks and CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and the response identifies the result with X-Page-Verdict and X-Billed headers. An MCP server exposes take_screenshot, get_page_info, and capture_pdf to Claude, Cursor, and other MCP clients. The Free plan includes 1,000 shots per month with no card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.
Selection checklist
- □ Confirm whether HTML must remain the source format.
- □ List JavaScript, fonts, images, external assets, and authentication requirements.
- □ Test print CSS, page breaks, long tables, headers, footers, SVG, and right-to-left content.
- □ Verify browser or native binary installation in every deployment target.
- □ Review current releases, supported .NET targets, licenses, and security guidance.
- □ Define timeouts, retries, concurrency limits, logging, and URL egress controls.
- □ Compare cold-start and warm-render performance on representative documents.
- □ Keep regression fixtures and inspect output after dependency upgrades.
FAQ
Can I use Playwright for .NET only for testing?
No. Its official documentation presents it as an end-to-end testing tool, but the browser automation APIs can also be used manually. Verify the current PDF API and engine behavior for your version.
Is DinkToPdf a Chromium renderer?
No. It wraps wkhtmltopdf, whose project identifies Qt WebKit as the rendering engine.
Which option preserves existing HTML templates?
PuppeteerSharp, Playwright for .NET, DinkToPdf, and HTML Renderer all accept HTML-oriented workflows, but their CSS and JavaScript behavior differs. Test the actual templates.
Should I choose QuestPDF for an HTML conversion project?
Only if you are willing to author the document in C# instead of preserving HTML. It is an adjacent change of approach.
Can a library choice guarantee identical PDFs across machines?
No. Browser version, fonts, operating-system libraries, locale, timezone, assets, and timing can all affect output. Pin the environment and run regression checks.
