Do these 3 things before closing this tab:
1Fix the driver behind crashes, sound loss and screen glitches2Clear out junk files and repair common Windows errors3Scan for outdated or missing drivers - takes under a minuteUse a browser-backed renderer when the existing HTML, CSS, and JavaScript must remain intact. For ASP.NET applications, Playwright for .NET with Chromium is the most direct way to reproduce a browser’s print output. A direct HTML converter such as SelectPdf can be simpler when its API and licensing fit your workload. If you can redesign the document as C# components, QuestPDF is a code-first alternative, not an HTML converter.
The correct choice depends on HTML fidelity, JavaScript requirements, deployment constraints, throughput, page limits, and licensing eligibility. No option is universally best.
Choose the rendering model first
| Requirement | Best starting point | Important qualification |
|---|---|---|
| Keep existing HTML, CSS, fonts, images, and JavaScript | Playwright for .NET and Chromium | Installs and runs a browser; test the target hosting environment. |
| Convert an HTML string or URL through a focused .NET API | SelectPdf | The vendor documents a free Community Edition limited to five pages per document; commercial terms and current framework support must be checked. |
| Create a stable document layout in C# | QuestPDF | Code-first; it does not render arbitrary existing HTML. |
Option 1: Render HTML with Playwright for .NET
Install the package and Chromium
- Add the package:
dotnet add package Microsoft.Playwright - Build the project so the Playwright installation script is generated.
- Run that script to install Chromium, for example from the project output:
pwsh bin/Debug/net8.0/playwright.ps1 install chromiumAdjust the framework directory to your project. Chromium can run headlessly.
Browser binaries are a deployment dependency. Include the installation step in your image or release process, and verify that the hosting account can start Chromium and write to its temporary directories.
Minimal conversion service
using Microsoft.Playwright;
public sealed class HtmlPdfRenderer : IAsyncDisposable
{
private readonly IPlaywright _playwright;
private readonly IBrowser _browser;
private HtmlPdfRenderer(IPlaywright playwright, IBrowser browser)
{
_playwright = playwright;
_browser = browser;
}
public static async Task<HtmlPdfRenderer> CreateAsync()
{
var playwright = await Playwright.CreateAsync();
var browser = await playwright.Chromium.LaunchAsync(new BrowserTypeLaunchOptions
{
Headless = true
});
return new HtmlPdfRenderer(playwright, browser);
}
public async Task<byte[]> RenderUrlAsync(string url, CancellationToken cancellationToken = default)
{
await using var context = await _browser.NewContextAsync();
var page = await context.NewPageAsync();
await page.GotoAsync(url, new PageGotoOptions
{
WaitUntil = WaitUntilState.NetworkIdle,
Timeout = 90_000
});
await page.EmulateMediaAsync(new PageEmulateMediaOptions
{
Media = Media.Screen
});
return await page.PdfAsync(new PagePdfOptions
{
Format = "A4",
PrintBackground = true,
Margin = new Margin
{
Top = "16mm", Bottom = "16mm", Left = "14mm", Right = "14mm"
}
});
}
public async ValueTask DisposeAsync()
{
await _browser.CloseAsync();
_playwright.Dispose();
}
}
Register one renderer or browser process for the application lifetime rather than launching a new browser for every request. Create isolated contexts or pages per job, limit concurrency, and close them in a finally path. The appropriate pooling and queue limits depend on your CPU, memory, PDF size, and hosting platform.
#1 Best Overall
Convert HTML already held by the application
public async Task<byte[]> RenderHtmlAsync(string html)
{
await using var context = await _browser.NewContextAsync();
var page = await context.NewPageAsync();
await page.SetContentAsync(html, new PageSetContentOptions
{
WaitUntil = WaitUntilState.NetworkIdle,
Timeout = 90_000
});
await page.EmulateMediaAsync(new PageEmulateMediaOptions { Media = Media.Print });
return await page.PdfAsync(new PagePdfOptions
{
Format = "Letter",
PrintBackground = true,
PreferCSSPageSize = true,
DisplayHeaderFooter = true,
HeaderTemplate = "",
FooterTemplate = "/",
Margin = new Margin { Top = "20mm", Bottom = "20mm", Left = "15mm", Right = "15mm" }
});
}
Use Media.Print for print styles (the default PDF behavior). Call EmulateMediaAsync with Media.Screen when the screen stylesheet is the intended design. The PDF API supports paper formats, explicit width and height, margins, headers and footers, and page ranges such as 1-3 or 5. By default, page.pdf() generates a PDF with modified colors for printing; use CSS -webkit-print-color-adjust when exact color reproduction is required.
Make the page deterministic before capture
- Wait for a meaningful selector, not only navigation completion, when JavaScript builds the content:
await page.WaitForSelectorAsync("#invoice-ready"); - Wait for fonts and images where necessary:
await page.EvaluateAsync("document.fonts.ready");and inspect image loading in page code. - Use absolute, reachable asset URLs or a controlled base URL for relative links.
- Define print page breaks with CSS such as
break-before,break-inside, andbreak-after; test long tables and repeating headers. - Use a fixed timezone, locale, cookies, authorization headers, or user agent when the page changes by visitor context.
ASP.NET endpoint
[ApiController]
[Route("pdf")]
public sealed class PdfController : ControllerBase
{
private readonly HtmlPdfRenderer _renderer;
public PdfController(HtmlPdfRenderer renderer) => _renderer = renderer;
[HttpGet]
public async Task<IActionResult> Get(CancellationToken cancellationToken)
{
var bytes = await _renderer.RenderUrlAsync("https://your-site.example/report", cancellationToken);
return File(bytes, "application/pdf", "report.pdf");
}
}
Validate or allow-list destination URLs if users can supply them. Otherwise a screenshot endpoint can become a server-side request forgery path. Restrict outbound networks, protect credentials, and avoid placing secrets in query strings or rendered HTML.
Option 2: SelectPdf for direct HTML or URL conversion
SelectPdf documents C# conversion from an HTML string and from a URL. Its documented settings include page size, orientation, margins, web-page width, and a Chromium rendering option. This model can be attractive when you want a conversion object instead of managing browser contexts directly.
Rank #2
var converter = new SelectPdf.HtmlToPdf();
converter.Options.PdfPageSize = SelectPdf.PdfPageSize.A4;
converter.Options.PdfPageOrientation = SelectPdf.PdfPageOrientation.Portrait;
converter.Options.MarginTop = 16;
converter.Options.MarginBottom = 16;
converter.Options.WebPageWidth = 1024;
converter.Options.RenderingEngine = SelectPdf.RenderingEngine.Chromium;
var document = converter.ConvertHtmlString(html);
document.Save("output.pdf");
document.Close();
The vendor states that its Community Edition permits five pages per document and that the commercial edition has no such page limit. Treat that as a vendor-published distinction, check the current package and target-framework support, and confirm licensing before production use. A five-page limit is per document, not a monthly allowance.
Free tools Windows power users keep installed
One-click scans. No signup required.
Option 3: QuestPDF when the layout can be rewritten in C#
QuestPDF is appropriate when your team can express the document as C# components: rows, columns, tables, text, images, and page decorations. It is not a drop-in renderer for arbitrary HTML and CSS.
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
QuestPDF.Settings.License = LicenseType.Community; // choose only when eligible
var document = Document.Create(container =>
{
container.Page(page =>
{
page.Size(PageSizes.A4);
page.Margin(30);
page.Content().Column(column =>
{
column.Item().Text("Monthly report").FontSize(22);
column.Item().Text("Generated in C# with a code-first layout.");
});
});
});
var pdfBytes = document.GeneratePdf();
return Results.File(pdfBytes, "application/pdf", "report.pdf");
Set the library license once during application startup or initialization, using the license type your organization and deployment actually qualify for. Review current terms before release.
What to test before choosing
- HTML fidelity: CSS grids, web fonts, SVG, canvas, JavaScript-generated sections, lazy images, and external assets.
- Print behavior: paper size, orientation, margins, print versus screen media, colors, page ranges, headers, footers, and page breaks.
- Operations: browser installation, sandbox permissions, memory growth, concurrency limits, timeouts, cancellation, and recovery after a crashed browser.
- Security: URL allow-lists, SSRF protection, authentication handling, untrusted HTML isolation, and outbound-request restrictions.
- Commercial fit: page limits, license eligibility, target-framework support, and total terms rather than only an initial package price.
There are no independent latency or fidelity benchmarks established here. Measure representative documents in the exact operating system, browser version, container, and traffic pattern you will deploy.
Troubleshooting
Chromium will not launch
Confirm that the browser binaries were installed for the deployed user, that executable dependencies exist in the image, and that the process has a writable temporary directory. Capture browser stderr and avoid assuming a developer workstation matches production.
The PDF is blank or missing dynamic content
Navigation completion may occur before the application finishes rendering. Wait for a stable selector or an application-specific ready signal, then verify fonts, images, API calls, cookies, and authorization inside the browser context.
Rank #4
The colors differ from the web page
PDF generation uses print media by default and may adjust colors. Choose screen media when appropriate, set PrintBackground, and apply -webkit-print-color-adjust: exact to elements whose colors must be preserved.
Pages break in the wrong places
Set the intended page size and margins, use print-specific CSS break rules, avoid oversized unbreakable containers, and test tables with unusually long rows.
Requests time out or overload the server
Set navigation and PDF timeouts, cancel abandoned requests, cap concurrent jobs, reuse the browser, and queue work when PDFs are large. Record URL, duration, page count, failure category, and browser errors without logging secrets.
Recommended Free Tools
Best Value
Or skip the browser setup
ScreenshotNeo provides a website screenshot API and MCP server. It accepts consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and response headers identify the page verdict and billing status.
For a one-call image, see the ScreenshotNeo documentation:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://your-site.example/report -o report.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://your-site.example/report"}, timeout=90)
r.raise_for_status()
open("report.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://your-site.example/report' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = Buffer.from(await res.arrayBuffer());
ScreenshotNeo also supports PDF capture, full-page and element shots, device and viewport settings, JavaScript and CSS, waits, request blocking, cookies and headers, geolocation, resizing, caching, signed links, asynchronous webhooks, bulk capture, and an MCP server with take_screenshot, get_page_info, and capture_pdf tools for AI clients. The Free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000. Create a free ScreenshotNeo account.
Frequently Asked Questions
Can Playwright generate a PDF without displaying a browser window?
Yes. Launch Chromium with headless mode enabled; the browser still needs to be installed on the host.
Should I use HTML or QuestPDF for a new report?
Keep HTML when browser rendering and existing templates matter. Choose QuestPDF when the document can be authored and maintained as C# layout components.
Is SelectPdf Community Edition suitable for a 20-page invoice pack?
The vendor-stated five-page-per-document Community Edition limit means you must verify commercial licensing or choose another approach.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

