Free .NET Core Libraries for Converting HTML to PDF
Compare free .NET Core HTML-to-PDF libraries, with runnable examples, limits, deployment notes, troubleshooting, and a browser-based alternative.
Short answer: choose SelectPdf Community Edition when a documented free tier and simple API matter and every document fits within five pages. Choose DinkToPdf when you can ship native wkhtmltopdf binaries and accept Qt WebKit rendering. Choose PuppeteerSharp when modern CSS and JavaScript require a real headless Chromium browser. There is no neutral benchmark proving one is universally fastest or most accurate, so validate your own templates, fonts, assets, and container image.
1. Compare the free options
| Library | Rendering engine | Free-path constraint | Operational cost | Best fit |
|---|---|---|---|---|
| SelectPdf Community Edition | Vendor engine advertising HTML5, CSS3, and JavaScript | Five pages per generated PDF | Packaged .NET converter | Simple reports and short documents |
| DinkToPdf + wkhtmltopdf | Qt WebKit | No stated page cap in the cited project; native engine required | Copy the correct OS/CPU binary and manage P/Invoke | Teams comfortable managing native dependencies |
| PuppeteerSharp | Headless Chromium | Open-source components; you manage browser acquisition and runtime | Chromium download, startup, sandbox, and memory | Browser-like CSS, JavaScript, and font behavior |
SelectPdf says its Community Edition is free for any use, including production, supports .NET Core and .NET 5–10, accepts URLs, files, and strings, and caps each PDF at five pages. Its commercial package adds longer documents and features such as PDF/A, PDF/UA, forms, portfolios, editing, and PDF-to-text/image operations. Check the Community Edition documentation before committing to a page-limited workflow.
DinkToPdf is an MIT .NET Core P/Invoke wrapper around wkhtmltopdf. wkhtmltopdf is an LGPLv3 command-line tool using Qt WebKit. The wrapper does not remove the native binary requirement: publish the matching library for every target operating system and architecture. The project documents BasicConverter for single-threaded use and SynchronizedConverter for multithreaded applications and web servers; register the converter as a singleton.
PuppeteerSharp is an MIT .NET port of the official Puppeteer API. Its documented targets include .NET Core 2.0 or later through its .NET Standard 2.0 build and a .NET 8 build. It downloads or launches Chromium, navigates like a browser, waits for fonts or other conditions, and calls PdfAsync. See the official PDF example.
2. SelectPdf Community Edition
Install the package named Select.HtmlToPdf from NuGet, then convert a URL or HTML string. The exact package and API can change, so confirm the current package instructions in SelectPdf’s documentation.
using SelectPdf;
var converter = new HtmlToPdf();
converter.Options.PdfPageSize = PdfPageSize.A4;
converter.Options.PdfPageOrientation = PdfPageOrientation.Portrait;
converter.Options.MarginTop = 20;
converter.Options.MarginBottom = 20;
converter.Options.MarginLeft = 18;
converter.Options.MarginRight = 18;
PdfDocument document = converter.ConvertUrl("https://example.com/invoice/123");
document.Save("invoice.pdf");
document.Close();
For raw markup, use the corresponding HTML conversion method documented by your installed version. Set a base URL when the markup references relative CSS, images, or fonts. Treat the five-page cap as a hard product decision: split long reports into sections, use another library, or evaluate the commercial package.
3. DinkToPdf and wkhtmltopdf
Add the DinkToPdf NuGet package, copy the native wkhtmltopdf library into your application output, and register one synchronized converter for a web server. The native filename and folder differ by platform; follow the project’s deployment instructions rather than assuming a Linux binary works in Windows or a different CPU architecture.
using DinkToPdf;
using DinkToPdf.Contracts;
using Microsoft.Extensions.DependencyInjection;
var services = new ServiceCollection();
services.AddSingleton(typeof(IConverter), new SynchronizedConverter(new PdfTools()));
using var provider = services.BuildServiceProvider();
var converter = provider.GetRequiredService<IConverter>();
var document = new HtmlToPdfDocument
{
GlobalSettings = new GlobalSettings
{
ColorMode = ColorMode.Color,
Orientation = Orientation.Portrait,
PaperSize = PaperKind.A4,
Margins = new MarginSettings { Top = 15, Bottom = 15, Left = 15, Right = 15 },
Out = "report.pdf"
},
Objects =
{
new ObjectSettings
{
Page = "https://example.com/report/123",
WebSettings = new WebSettings { DefaultEncoding = "utf-8", LoadImages = true },
HeaderSettings = new HeaderSettings { FontSize = 8, Right = "Page [page] of [toPage]" }
}
}
};
converter.Convert(document);
For protected pages, supply cookies or custom headers through the options supported by your wkhtmltopdf build. Test JavaScript-heavy pages carefully: Qt WebKit is a different browser engine from Chromium, and modern layout features may render differently.
4. PuppeteerSharp with headless Chromium
Install PuppeteerSharp, download a compatible browser during deployment or startup, and run Chromium in the target environment. In containers, configure the sandbox according to your security model; do not copy flags blindly from development into production.
using PuppeteerSharp;
var fetcher = new BrowserFetcher();
await fetcher.DownloadAsync();
await using var browser = await Puppeteer.LaunchAsync(new LaunchOptions
{
Headless = true,
Args = new[] { "--no-sandbox", "--disable-setuid-sandbox" }
});
await using var page = await browser.NewPageAsync();
await page.SetViewportAsync(new ViewPortOptions { Width = 1280, Height = 900, DeviceScaleFactor = 1 });
await page.GoToAsync("https://example.com/report/123", WaitUntilNavigation.Networkidle0);
await page.EvaluateExpressionAsync("document.fonts.ready");
await page.PdfAsync("report.pdf", new PdfOptions
{
Format = PaperFormat.A4,
PrintBackground = true,
MarginOptions = new MarginOptions { Top = "15mm", Bottom = "15mm", Left = "15mm", Right = "15mm" }
});
For server-generated markup, replace navigation with SetContentAsync(html). Set a base URL or absolute asset URLs so CSS, images, and web fonts resolve. Wait for a selector, a known application event, or document.fonts.ready before creating the PDF.
5. Options that determine output quality
- Page geometry: set paper size, orientation, margins, scale, and print backgrounds explicitly.
- Assets: use absolute URLs or a correct base URL; ensure the renderer can reach private assets with headers or cookies.
- Fonts: install the required fonts in the runtime image and wait for font loading before capture.
- JavaScript: wait for network idle only when the page eventually becomes idle; otherwise wait for an application-specific selector or event.
- Pagination: use print CSS such as
break-inside: avoid,break-before, and@page, then inspect overflow at every supported paper size. - Security: restrict arbitrary URL input, block access to internal networks where appropriate, and sanitize untrusted HTML.
6. Deployment, performance, and reliability
Benchmark in the same OS image, CPU architecture, font set, network conditions, and concurrency level used in production. Measure cold startup separately from warm conversions. Chromium and native WebKit processes consume memory; use a bounded queue, recycle unhealthy workers, and enforce navigation and conversion timeouts.
Cache immutable templates and assets, avoid downloading the same large images repeatedly, and reuse a browser or converter where the library supports safe reuse. For DinkToPdf web servers, use the documented synchronized singleton. For PuppeteerSharp, limit concurrent pages and monitor child-process count. A successful HTTP response does not prove visual correctness: compare representative PDFs, page counts, missing-font behavior, external images, and JavaScript-rendered sections.
7. Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Native library not found | DinkToPdf cannot load the wkhtmltopdf binary | Copy the correct native file to the published output and verify OS/CPU architecture and executable permissions. |
| Blank or partially rendered PDF | Navigation or JavaScript finished after conversion | Wait for a selector, network idle, an application event, or fonts; increase timeouts only after identifying the slow dependency. |
| Missing images, CSS, or fonts | Relative URLs, blocked network access, or absent runtime fonts | Use absolute URLs or a base URL, allow required hosts, install fonts, and inspect renderer logs. |
| Modern CSS differs from the browser | wkhtmltopdf uses Qt WebKit | Test WebKit-specific output or move the template to PuppeteerSharp/Chromium. |
| Chromium fails to start in Linux | Missing shared libraries, sandbox restrictions, or an incompatible browser build | Install runtime dependencies, use a compatible downloaded browser, and configure sandboxing deliberately. |
| Request hangs | Never-ending requests, websocket activity, or an unreachable asset | Set navigation and conversion timeouts, wait on a deterministic condition, and block or mock unnecessary resources. |
| Output exceeds SelectPdf’s limit | Community Edition maximum is five pages | Split the document, select another open-source route, or evaluate SelectPdf’s commercial package. |
8. Or skip the browser setup
If your application mainly needs a dependable URL-to-PDF request, ScreenshotNeo exposes a GET endpoint that returns a PDF as well as PNG, JPEG, or WebP screenshots. The API accepts the URL and capture options, so your service does not need to package wkhtmltopdf or manage Chromium.
See the ScreenshotNeo API documentation for the current parameter reference. A minimal PDF request is:
curl -G "https://api.screenshotneo.com/v1/shot" \
-d access_key=YOUR_API_KEY \
--data-urlencode url=https://example.com/report/123 \
-d format=pdf \
-o report.pdf
The same endpoint can be called from Python or Node.js:
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://example.com/report/123", "format": "pdf"},
timeout=90,
)
r.raise_for_status()
open("report.pdf", "wb").write(r.content)
const q = new URLSearchParams({
access_key: 'YOUR_API_KEY',
url: 'https://example.com/report/123',
format: 'pdf'
});
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`ScreenshotNeo returned ${res.status}`);
const data = await res.arrayBuffer();
await Bun.write('report.pdf', data);
ScreenshotNeo removes cookie and consent banners, newsletter popups, and chat widgets before capture; bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and response headers report the page verdict and billing result. It also provides an MCP server for AI agents, including Claude and Cursor. The free plan includes 1,000 screenshots per month without a card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.
9. Decision checklist
- Choose SelectPdf if five pages per document covers the requirement and you want the shortest integration.
- Choose DinkToPdf if Qt WebKit output is acceptable and you can package native binaries reliably.
- Choose PuppeteerSharp if Chromium compatibility, JavaScript, and modern CSS outweigh browser deployment work.
- Render your longest and most dynamic templates in the production image before selecting a library.
- Use ScreenshotNeo when a hosted URL-to-PDF call is preferable to maintaining a renderer process.
10. FAQ
Which option is truly free for production?
SelectPdf states that its Community Edition is free for any use, including production, with a five-page cap. DinkToPdf/wkhtmltopdf and PuppeteerSharp are open-source components, but you still operate their native or browser dependencies.
Which library supports the newest CSS?
PuppeteerSharp drives headless Chromium, so it is generally the browser-like choice. Validate the exact Chromium version and your templates rather than relying on a generic compatibility claim.
Can I convert an HTML string instead of a URL?
Yes. SelectPdf documents raw HTML conversion, PuppeteerSharp supports SetContentAsync, and DinkToPdf can receive HTML through its object settings. Supply a base URL for relative assets.
Is there a universal fastest library?
No neutral benchmark in the cited sources establishes that. Cold starts, page complexity, fonts, network assets, and concurrency dominate real results.
What should I test before launch?
Test page breaks, fonts, images, JavaScript timing, private resources, long documents, timeouts, concurrent requests, container architecture, and failure handling.
