# HTML to PDF in C# and .NET
HTML to PDF in C# and .NET Core with free PuppeteerSharp and Playwright options, ASP.NET Core examples, and a one-call HttpClient API.
**Quick answer.** Post your HTML to `https://html-pdf.com/v1/pdf` with a Bearer key and save the bytes. No NuGet package or Chrome needed.
```csharp
using System.Net.Http.Json;
using var http = new HttpClient();
http.DefaultRequestHeaders.Add("Authorization", "Bearer " + Environment.GetEnvironmentVariable("HTML_PDF_KEY"));
var res = await http.PostAsJsonAsync("https://html-pdf.com/v1/pdf", new { html = "
Hello
" });
await File.WriteAllBytesAsync("hello.pdf", await res.Content.ReadAsByteArrayAsync());
```
There's no HTML to PDF converter built into .NET. You pick between free browser-based libraries, commercial libraries (IronPDF, Syncfusion, SelectPDF and others), or an HTTP API.
This guide shows the free DIY routes with working code. Then it covers the API route for when you don't want a browser in your app.
## Free options: drive headless Chrome from C#
The most accurate free approach is to let Chromium do the layout, exactly like Chrome's "Save as PDF". Two maintained libraries do this.
### PuppeteerSharp
```bash
dotnet add package PuppeteerSharp
```
```csharp
using PuppeteerSharp;
using PuppeteerSharp.Media;
// Downloads a compatible Chrome build the first time (cache it in CI and Docker images)
await new BrowserFetcher().DownloadAsync();
await using var browser = await Puppeteer.LaunchAsync(new LaunchOptions { Headless = true });
await using var page = await browser.NewPageAsync();
await page.SetContentAsync("Invoice #042
Total: $120.00
",
new NavigationOptions { WaitUntil = new[] { WaitUntilNavigation.Networkidle0 } });
await page.PdfAsync("invoice.pdf", new PdfOptions
{
Format = PaperFormat.A4,
PrintBackground = true,
MarginOptions = new MarginOptions { Top = "15mm", Bottom = "15mm", Left = "15mm", Right = "15mm" },
DisplayHeaderFooter = true,
HeaderTemplate = "",
FooterTemplate = "" +
"Page of
",
});
```
### Playwright for .NET
```bash
dotnet add package Microsoft.Playwright
pwsh bin/Debug/net8.0/playwright.ps1 install chromium
```
```csharp
using Microsoft.Playwright;
using var playwright = await Playwright.CreateAsync();
await using var browser = await playwright.Chromium.LaunchAsync();
var page = await browser.NewPageAsync();
await page.GotoAsync("https://example.com", new() { WaitUntil = WaitUntilState.NetworkIdle });
await page.PdfAsync(new PagePdfOptions
{
Path = "page.pdf",
Format = "A4",
PrintBackground = true,
Margin = new Margin { Top = "15mm", Bottom = "15mm", Left = "15mm", Right = "15mm" },
});
```
PDF generation in Playwright works with Chromium only. Adjust the `net8.0` path to your target framework.
### What it costs you to run
Both libraries are free, but you're now operating a browser. Here is what that involves.
- **Deployment:** the Chrome binary has to be present (and the right version) on every server, container and build agent. Linux containers need extra system libraries.
- **Azure App Service and Functions:** the Windows sandbox on some plans blocks the Win32 calls Chrome needs, so teams move to Linux containers or a separate rendering service.
- **Resources:** each browser instance takes significant memory. Reuse one browser, open a page per request, and dispose pages reliably.
- **Security:** rendering untrusted HTML or URLs inside your network needs care.
### Commercial libraries
IronPDF, Syncfusion HTML-to-PDF, SelectPDF, EvoPDF and others wrap a browser engine in a NuGet package with support. They're licensed per developer or per deployment.
Check each vendor's current licensing before you commit, because terms for SaaS and server use differ.
## The API route: HttpClient only
This section gives you a full C# example that needs nothing but `HttpClient`. html-pdf.com renders your HTML in headless Chromium and returns the PDF bytes, so there's no NuGet package and no Chrome to install.
```csharp
using System.Net.Http.Headers;
using System.Net.Http.Json;
using var http = new HttpClient { Timeout = TimeSpan.FromSeconds(60) };
http.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", Environment.GetEnvironmentVariable("HTML_PDF_KEY"));
var response = await http.PostAsJsonAsync("https://html-pdf.com/v1/pdf", new
{
html = "Invoice #042
Total: $120.00
",
format = "A4",
margin = "15mm",
footer_html = "" +
"Page of
",
max_pages = 20, // fail without charge if the PDF would be longer
});
if (!response.IsSuccessStatusCode)
throw new Exception(await response.Content.ReadAsStringAsync()); // {"error":{"code","message","hint"}}
await File.WriteAllBytesAsync("invoice.pdf", await response.Content.ReadAsByteArrayAsync());
Console.WriteLine($"Pages: {response.Headers.GetValues("X-Pages").First()}");
```
`PostAsJsonAsync` uses camelCase by default. The anonymous object's properties are already snake_case, so they're sent as written.
To convert a public page instead, send `url = "https://example.com"`.
## ASP.NET Core: return a PDF from an endpoint
Here is how to serve a PDF straight from a minimal API endpoint. Register a typed `HttpClient` once and stream the result.
```csharp
// Program.cs
using System.Net.Http.Headers;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHttpClient("htmlpdf", c =>
{
c.BaseAddress = new Uri("https://html-pdf.com/");
c.Timeout = TimeSpan.FromSeconds(60);
c.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", builder.Configuration["HtmlPdf:Key"]);
});
var app = builder.Build();
app.MapGet("/invoices/{id:int}/pdf", async (int id, IHttpClientFactory factory) =>
{
var html = $"Invoice {id}
"; // render a Razor view or template here, HTML-encoding user data
var client = factory.CreateClient("htmlpdf");
var res = await client.PostAsJsonAsync("v1/pdf", new { html, format = "A4", margin = "15mm" });
if (!res.IsSuccessStatusCode)
return Results.Problem(await res.Content.ReadAsStringAsync(), statusCode: 502);
return Results.Stream(await res.Content.ReadAsStreamAsync(), "application/pdf", $"invoice-{id}.pdf");
});
app.Run();
```
Store the key with `dotnet user-secrets set "HtmlPdf:Key" "hp_live_..."` in development and an environment variable (`HtmlPdf__Key`) in production.
Images and stylesheets must use absolute `https://` URLs or be inlined, because the renderer can't reach `localhost` or private networks. For local assets, send a multipart request with `index.html`, the files, and an `options` JSON field.
## Get an API key
One request gives you a key.
```bash
curl -X POST https://html-pdf.com/v1/signup -H "Content-Type: application/json" -d '{"email": "you@example.com"}'
```
Open the link we email you to confirm your address. That adds 100 free credits, and 1 credit = 1 PDF page. Packs start at $10 for 1,000 credits, purchased credits never expire, and failed renders cost nothing. See [pricing](https://html-pdf.com/pricing).
Related: [page breaks](https://html-pdf.com/html-to-pdf-page-breaks) · [headers, footers and page numbers](https://html-pdf.com/pdf-headers-footers-page-numbers) · [wkhtmltopdf alternative](https://html-pdf.com/wkhtmltopdf-alternative) (for DinkToPdf users) · [API docs](https://html-pdf.com/docs)
## FAQ
### How do I convert HTML to PDF in C#?
Use PuppeteerSharp or Playwright for .NET to drive headless Chrome for free, or post your HTML to `https://html-pdf.com/v1/pdf` with `HttpClient` if you don't want a browser on your servers.
### Is there a free HTML to PDF library for .NET?
Yes. PuppeteerSharp and Microsoft.Playwright are free and use Chromium, so the output matches Chrome's "Save as PDF". The html-pdf.com API also gives 100 free pages with no credit card.
### Does it work on Azure App Service?
Running Chrome yourself can fail on some Windows App Service and Functions plans because the sandbox blocks the Win32 calls Chrome needs. Calling the API over HTTPS works anywhere `HttpClient` does.
### Do credits expire?
No. Purchased credits never expire, packs cost $0.01 to $0.005 per page, and failed renders cost nothing.