Three C# routes: QuestPDF, Playwright for .NET, or HttpClient
To generate a PDF from a template in C#, the code-first route is QuestPDF, where the template is a C# class written with a fluent API and your data is an ordinary object. If your template is HTML, Playwright for .NET renders it in Chromium and saves a PDF. If the layout should be edited outside the codebase, a template API takes one HttpClient call.
This guide differs from the Python and Node.js guides on this site, which start from HTML templates. Here a code-first library comes first because the compiler checks your template against your data model.
Route 1: QuestPDF, a template written in C#
Prerequisites and license. QuestPDF is a NuGet package. Its quick start says the library is free for individuals and businesses with less than $1 million in annual gross revenue, for non-profits and for open source projects; larger companies need a paid license. The quick start has you set the license type in code.
- Install:
dotnet add package QuestPDF - Set the license. The quick start shows
QuestPDF.Settings.License = LicenseType.Evaluation;for learning; choose the type that matches your organization. - Start the document:
Document.Create(container => { container.Page(page => { /* layout */ }); }) - Inside the page, set the sheet:
page.Size(PageSizes.A4);andpage.Margin(2, Unit.Centimetre); - Fill the slots with your data:
page.Header().Text(model.InvoiceNumber);andpage.Content().Column(x => { x.Item().Text(model.CustomerName); }); - Add page numbers:
page.Footer().AlignCenter().Text(x => { x.Span("Page "); x.CurrentPageNumber(); }); - Write the file by chaining
.GeneratePdf("invoice.pdf");onto the document.
The "template" here is the method that takes model. Loops and conditions are plain C#.
Route 2: an HTML template rendered with Playwright for .NET
Choose this when a designer hands you HTML and CSS, or you need charts drawn by JavaScript.
- Install:
dotnet add package Microsoft.Playwright - Install the browsers after building:
pwsh bin/Debug/netX/playwright.ps1 install, replacing netX with your target folder such as net8.0. - Start Playwright and Chromium:
using var playwright = await Playwright.CreateAsync();thenawait using var browser = await playwright.Chromium.LaunchAsync(); - Open a page and load your filled HTML:
var page = await browser.NewPageAsync();thenawait page.SetContentAsync(html); - Save:
await page.PdfAsync(new() { Path = "invoice.pdf" });
PdfAsync returns the PDF bytes as well, and renders with print CSS media. Its documented options include Format, which defaults to Letter, and Margin. How you fill the HTML is up to you: Razor, a template engine, or string replacement for a simple document.
Route 3: a template API with HttpClient
Prerequisites. A CraftMyPDF account, a template ID and an API key. The Free plan allows 50 PDFs a month and 3 templates (pricing page, 3 October 2026).
- Create one shared client, as Microsoft's guidance recommends:
private static HttpClient sharedClient = new(); - Add the key through the client's
DefaultRequestHeadersproperty, using the header nameX-API-KEY. - Post the payload:
using HttpResponseMessage response = await sharedClient.PostAsJsonAsync("https://api.craftmypdf.com/v1/create", payload); - Fail fast on errors:
response.EnsureSuccessStatusCode(); - Read the result:
var result = await response.Content.ReadFromJsonAsync<CreateResult>();
payload is an object with template_id, data and export_type. CreateResult is your own record with status, file and transaction_ref, the fields the API reference documents. PostAsJsonAsync and ReadFromJsonAsync come from System.Net.Http.Json. See the CraftMyPDF API guide for the other parameters.
Worked example and how the three C# routes compare
An invoice with a header, a line-item table and a page number footer can be built on all three routes. The differences are in who maintains it and what runs.
| QuestPDF | Playwright for .NET | Template API (CraftMyPDF) | |
|---|---|---|---|
| Template is | C# code | HTML and CSS | A design in a hosted editor |
| Extra runtime | None | A Chromium download | None |
| Cost | Free under $1M revenue, then licensed | Open source | Free for 50 PDFs a month, then from $29 a month |
| Changed by | A C# developer | A front-end developer | Anyone with editor access |
| Type-checked against your model | Yes | No | No |
For most .NET teams that own their layouts, QuestPDF is enough and a paid API adds nothing. The API earns its fee when layout changes should not need a release. The three-route overview has the general version of this choice.
A mistake to avoid on each C# route
- QuestPDF: no license set. The quick start marks the license line as a to-do for a reason. Decide which license applies before you ship, since the free tier depends on revenue.
- Playwright: browsers not installed on the server. The NuGet package alone is not enough; the
playwright.ps1 installstep must run in the deployment environment too. - HttpClient: a new client per request. Microsoft's documentation recommends reusing one instance for the life of the application.
- API: keeping the link. The returned URL expires after 5 minutes by default; download the file or set
expiration.
Java teams face a similar choice with different libraries; see the Java version of this guide.
Sources used for this page
The facts above come from the pages below, read on 3 October 2026. We have not used these products hands-on. Prices and terms change, so confirm them on the vendor's site before you buy.
- Quick start | QuestPDF — Vendor documentation · questpdf.com · checked 2026-10-03
- Getting started: Library | Playwright .NET — Vendor documentation · playwright.dev · checked 2026-10-03
- Page | Playwright .NET — Vendor documentation · playwright.dev · checked 2026-10-03
- Make HTTP requests with the HttpClient class - .NET — Vendor documentation · learn.microsoft.com · checked 2026-10-03
- CraftMyPDF API Reference (OpenAPI specification) — Vendor documentation · craftmypdf.com · checked 2026-10-03
- Pricing - CraftMyPDF.com — Vendor pricing page · craftmypdf.com · checked 2026-10-03