EvoPdf Next – HTML to PDF Converter for .NET
C# · .NET 6 to 10 · .NET Framework 4.6.2+ · Windows, Linux and macOS

Convert HTML to PDF in C#

This guide takes an HTML page or an HTML string and turns it into a PDF from C# code, using EvoPdf Next, a .NET library with a Chromium-based rendering engine inside the NuGet package. It covers the three lines that do the conversion, how to return the PDF from an ASP.NET Core action, page size and margins, headers and footers with page numbers, page breaks, PDF/UA and PDF/A output and what to set when the page loads content with JavaScript. Every sample compiles against the current package and the same code runs on Windows, Linux and macOS.

Version 14.84.0. Evaluate without registration; the demo output is watermarked until you set a license key. Perpetual licenses from 450 USD, for any number of developers.

ConsoleCLI
dotnet add package EvoPdf.Next.HtmlToPdf.Windows
Program.csC#
using EvoPdf.Next;

var converter = new HtmlToPdfConverter();

// a web page, by URL
byte[] pdf = converter.ConvertUrl("https://www.evopdf.com");
File.WriteAllBytes("page.pdf", pdf);

// an HTML string, with the base URL for its images and CSS
converter = new HtmlToPdfConverter();
pdf = converter.ConvertHtml("<h1>Hello World</h1>",
    "https://www.evopdf.com");
File.WriteAllBytes("string.pdf", pdf);
This is the whole conversion. Everything below is about controlling the result. Convert your own HTML in the online demo →
  • Windows 10 / 11 / Server 2016–2025, x64 and ARM64
  • Linux 64-bit, x64 and ARM64
  • macOS 12+ on Apple Silicon
  • .NET 6 → 10, .NET Standard 2.0
  • .NET Framework 4.6.2 – 4.8.1
  • Azure App Service, Functions, Cloud Services & VMs · Docker
Step 1

Install the package for your platform

One NuGet package per platform. Each contains the same .NET Standard 2.0 assembly plus the native rendering engine for that operating system, so there is nothing to install on the server and no browser to download at runtime.

Windows

dotnet add package EvoPdf.Next.HtmlToPdf.Windows

x64. For Windows on ARM use EvoPdf.Next.HtmlToPdf.Windows.Arm64.

Linux

dotnet add package EvoPdf.Next.HtmlToPdf.Linux

x64. For ARM64 use EvoPdf.Next.HtmlToPdf.Linux.Arm64. A few system libraries are required; see Getting Started on Linux.

macOS

dotnet add package EvoPdf.Next.HtmlToPdf.MacOS

Apple Silicon. See Getting Started on macOS.

An application published for both Windows and Linux can reference EvoPdf.Next.HtmlToPdf instead, a metapackage that pulls in the x64 packages for both. The public API is identical across packages: the code in this guide does not change from one platform to another.
Step 2

Convert a URL or an HTML string

The HtmlToPdfConverter class has two sources and two destinations: a URL or an HTML string, into a byte array or into a file. Create a new converter instance for each conversion.

To a byte array
using EvoPdf.Next;

var converter = new HtmlToPdfConverter();

byte[] fromUrl = converter.ConvertUrl(
    "https://www.evopdf.com");

converter = new HtmlToPdfConverter();
byte[] fromHtml = converter.ConvertHtml(
    "<h1>Invoice 1042</h1><img src='logo.png'>",
    "https://www.example.com/assets/");
To a file
using EvoPdf.Next;

var converter = new HtmlToPdfConverter();

converter.ConvertUrlToFile(
    "https://www.evopdf.com", "page.pdf");

converter = new HtmlToPdfConverter();
converter.ConvertHtmlToFile(
    "<h1>Invoice 1042</h1>",
    "https://www.example.com/assets/",
    "invoice.pdf");

The second argument of ConvertHtml is the base URL. Relative paths in the HTML (logo.png, styles.css, fonts/inter.woff2) are resolved against it exactly as a browser would resolve them for a page served from that address. Pass null when the HTML string has no external resources or uses absolute URLs only. A local folder works too, as a file:/// URL.

Each method has an Async variant with the same name (ConvertUrlAsync, ConvertHtmlAsync and the ToFileAsync pair) that takes a CancellationToken and follows the task-based pattern. Because every conversion gets its own converter instance, running several in parallel is one Task.WhenAll:

Parallel.csC#
string[] urls = { "https://example.com/report/1", "https://example.com/report/2", "https://example.com/report/3" };

Task<byte[]>[] tasks = urls
    .Select(u => new HtmlToPdfConverter().ConvertUrlAsync(u, CancellationToken.None))
    .ToArray();

byte[][] pdfs = await Task.WhenAll(tasks);

How many run at the same time is a single global setting; the rest is scheduling. The documentation topic Convert Multiple HTML Pages to PDF in Parallel has the full sample and the online demo runs it with your own list of pages.

Step 3

Return the PDF from ASP.NET Core

In a web application the byte array goes straight into a FileContentResult. The same code works in an MVC controller, a Razor Pages handler or a Minimal API endpoint.

MVC controller
using EvoPdf.Next;
using Microsoft.AspNetCore.Mvc;

public class ReportsController : Controller
{
    [HttpGet("reports/{id}/pdf")]
    public async Task<IActionResult> Pdf(int id)
    {
        string url = Url.Action("Details", "Reports",
            new { id }, Request.Scheme);

        var converter = new HtmlToPdfConverter();
        byte[] pdf = await converter.ConvertUrlAsync(url,
            HttpContext.RequestAborted);

        // inline in the browser; add a file name to download
        return File(pdf, "application/pdf");
        // return File(pdf, "application/pdf", $"report-{id}.pdf");
    }
}
Minimal API
using EvoPdf.Next;

var app = WebApplication.Create(args);

// GET /pdf?url=https://example.com/report/1042
app.MapGet("/pdf", async (string url, CancellationToken ct) =>
{
    var converter = new HtmlToPdfConverter();
    byte[] pdf = await converter.ConvertUrlAsync(url, ct);
    return Results.File(pdf, "application/pdf", "page.pdf");
});

app.Run();
To convert a Razor view, render it to a string with the view engine and pass the string to ConvertHtml with the site's base URL, or point ConvertUrl at the view's own address. When the page sits behind a login, render it to a string in the same request, as in Convert a HTML Page to PDF in Same Session, send the session cookies with the request, as in Add Cookies to HTML Page Request, or let the converter authenticate itself, as in Convert HTML Pages with Authentication.
Step 4

Page size, orientation and margins

By default the page is A4 and the HTML is rendered as in a 1024 pixel browser window scaled to fit it, at 77.47 percent: the output most people expect from a web page. For another page size, call FitBrowserWindowToPage with it; the scale follows the page and the margins you set. A document designed for the paper, an invoice or a report with @page rules, gets LayoutAtPageWidth instead: the HTML is laid out at the width between the margins, 793 pixels on an A4 page without margins, and drawn without scaling. And a page that should keep its own width gets PageWidthFromBrowserWindow: the PDF page becomes as wide as the window, one CSS pixel being 0.75 points.

A template designed for A4, with margins
var converter = new HtmlToPdfConverter();
var options = converter.PdfDocumentOptions;

// margins are in points, 72 points = 1 inch
options.LeftMargin = 36;
options.RightMargin = 36;
options.TopMargin = 54;
options.BottomMargin = 54;

// laid out at the width between the margins, no scaling
converter.LayoutAtPageWidth(PdfPageSize.A4, PdfPageOrientation.Portrait);

byte[] pdf = converter.ConvertUrl(url);
Page width from the HTML
var converter = new HtmlToPdfConverter();

// the PDF page becomes as wide as a 1024 px browser window, content drawn 1:1;
// the second argument makes one tall page instead of paginated output
converter.PageWidthFromBrowserWindow(1024, singlePage: true);

byte[] pdf = converter.ConvertUrl(url);

The predefined sizes (A4, Letter, Legal, the A and B series, Arch and Ledger) are fields of PdfPageSize; a custom size takes the width and the height in points. If the HTML declares its own size in an @page CSS rule, set PreferCssPageSize to true and the converter follows the stylesheet. How the page size, the layout width and the zoom work together, with the settings for a web page on A4, a template designed for paper and a single page, is in HTML to PDF Page Setup and Scaling. The full list of options is in HTML to PDF Converter Options.

Step 5

Headers and footers with page numbers

Headers and footers are HTML too. The quickest way is the browser mode: enable it and give the converter a header template and a footer template, with CSS classes that the engine fills in on every page.

HeaderFooter.csC#
var converter = new HtmlToPdfConverter();
var options = converter.PdfDocumentOptions;

options.EnableHeaderFooter = true;

options.HeaderTemplate =
    "<div style='font-size:9px;width:100%;text-align:center;color:#555'>" +
    "<span class='title'></span></div>";

options.FooterTemplate =
    "<div style='font-size:9px;width:100%;text-align:center;color:#555'>" +
    "Page <span class='pageNumber'></span> of <span class='totalPages'></span>" +
    "</div>";

// leave room for them
options.TopMargin = 50;
options.BottomMargin = 50;

byte[] pdf = converter.ConvertUrl(url);

The classes the engine recognizes are pageNumber, totalPages, title, url and date. When the header needs a logo, a table or a layout of its own, use the PdfHtmlHeader and PdfHtmlFooter objects instead: they take a full HTML document with a base URL and render it into the header and footer areas. Both approaches are described in HTML Header and Footer in Browser Mode and HTML Header and Footer with Page Numbers.

Step 6

Page breaks and repeated table headers

Pagination is controlled from the HTML, with the CSS the browser uses when printing. No C# is needed for the breaks themselves.

In C#
var converter = new HtmlToPdfConverter();

// repeat <thead> and <tfoot> on every page
converter.PdfDocumentOptions.RepeatTableHeaderFooter = true;

// render with the print stylesheet instead of the screen one
converter.MediaType = "print";

byte[] pdf = converter.ConvertUrl(url);
In the HTML
<!-- start a new page before this element -->
<h2 style="page-break-before: always">Chapter 2</h2>

<!-- keep a block on one page -->
<table style="page-break-inside: avoid">
  <thead><tr><th>Item</th><th>Amount</th></tr></thead>
  <tbody> ... </tbody>
</table>

The page-break-before, page-break-after and page-break-inside properties work as in Chrome's print preview, along with the newer break-before and break-inside properties. Details and a runnable sample: Insert Page Breaks in PDF Using CSS, Avoid Page Break Inside Elements and Repeat HTML Table Header and Footer.

Step 7

Bookmarks and a table of contents from the headings

Two options turn the heading structure of the HTML into a bookmarks panel in the PDF viewer and into a table of contents with page numbers inside the document. Any element can take part, not only h1 to h6, through a data-heading attribute.

In C#
var converter = new HtmlToPdfConverter();
var options = converter.PdfDocumentOptions;

// bookmarks panel, one entry per heading, nested by level
options.GenerateDocumentOutline = true;

// table of contents with page numbers, inside the document
options.GenerateTableOfContents = true;
options.TableOfContents.Title = "Contents";

// true: rendered in the html-to-pdf-toc element, inside the page
// false: on its own pages, in front of the document
options.TableOfContents.CreateInline = true;

byte[] pdf = converter.ConvertUrl(url);
In the HTML
<!-- inline mode renders the list here -->
<div id="html-to-pdf-toc"></div>

<h1>Annual report</h1>
<h2>Revenue</h2>

<!-- any element becomes an entry, with its own text -->
<div data-heading="2" data-heading-text="Appendix A: rates">
  ...
</div>

<!-- a real heading kept out of both -->
<h3 data-heading="false">Figure caption</h3>

The container decides where the table of contents goes. Leave the html-to-pdf-toc element out and set CreateInline to false and the converter puts the table of contents on pages of its own, in front of the document, which is what a report or a manual usually needs; keep the element and the list is laid out inside the page, in the flow of the content. Its look is a CSS stylesheet applied through TableOfContents.Style, its page numbers account for pages inserted before the HTML and can be offset for documents that will be merged later. The data-heading attributes apply in the default custom mode; the browser mode (UseBrowserOutlineMode, TableOfContents.UseBrowserMode) uses the standard heading tags only. Details and samples: Auto Create Hierarchical Bookmarks and Auto Create Table of Contents, each with its online demo.

Step 8

Generate PDF/UA and PDF/A documents

Accessibility and archival compliance are one property. The converter builds the tagged structure from the HTML (headings, lists, tables, alt text) and writes the metadata the standard requires; the output validates in veraPDF, PAC and Acrobat.

Standards.csC#
var converter = new HtmlToPdfConverter();

// accessible (PDF/UA-1) and archivable (PDF/A-2b) in one document
converter.PdfDocumentOptions.PdfStandard = PdfStandard.PdfUa1PdfA2b;

// other values: PdfUa1, PdfUa2, PdfA2a, PdfA3b, PdfA4, PdfUa2PdfA4 ...

converter.PdfDocumentInfo.Title = "Annual report 2026";

byte[] pdf = converter.ConvertUrl(url);

The HTML has to be well structured for the result to pass: one h1, headings in order, alt on images, a lang attribute on the root element. The supported standards and combinations are listed, with what each requires from the HTML, in Create PDF/UA and PDF/A Compliant Documents. You can also generate a PDF/UA or PDF/A document from your own HTML in the online demo and run it through veraPDF or PAC yourself.

Step 9

Pages that load content with JavaScript

The engine executes JavaScript and waits for the page to load before rendering. Pages that fetch data after load, animate charts or redirect need one or two extra settings.

Timing.csC#
var converter = new HtmlToPdfConverter();

// give asynchronous requests and animations time to finish, in seconds
converter.ConversionDelay = 2;

// maximum time for the whole conversion, in seconds (default 120)
converter.NavigationTimeout = 60;

// run a script after load, for example to expand a section
converter.ScriptToExecuteAfterLoad = "document.querySelector('#details').open = true;";
converter.WaitForAfterLoadScript = true;

byte[] pdf = converter.ConvertUrl(url);

For pages that know exactly when they are ready, the conversion can be triggered from the page's own JavaScript instead of a delay: see Select Conversion Triggering Mode. Custom headers, cookies and POST data for the request are covered in the same section of the documentation.

Step 10

Find where each HTML element landed in the PDF

Give the converter a CSS selector and after the conversion it reports, for every matching element, the page number and the rectangle in points where it was drawn. An element that runs across pages gets one rectangle per page. This is what turns the generated PDF into something you can post-process precisely.

Positions.csC#
var converter = new HtmlToPdfConverter();

// collect the position of every invoice line and of the signature box
converter.HtmlElementsInfoSelector = ".line-item, #signature";

byte[] pdf = converter.ConvertUrl(url);

foreach (HtmlElementInfo element in converter.HtmlElementsInfo.Elements)
{
    // one rectangle per page the element appears on
    foreach (PdfRenderedRectangle rect in element.RenderedRectangles)
    {
        Console.WriteLine($"{element.TagName}#{element.Id} page {rect.PageNumber}: " +
            $"{rect.Bounds.X}, {rect.Bounds.Y}, {rect.Bounds.Width} x {rect.Bounds.Height} pt");
    }
}

Each HtmlElementInfo carries the tag name, id, CSS class and text of the element; the collection can also be searched with FindById, FindByClass and FindByTagName. With the page and the rectangle in hand, the PDF editor from the Core component adds whatever the layout needs at exactly that spot. What developers use it for:

Signatures and annotations on the right spot

Place a digital signature appearance on the signature line of a contract, attach a note, a link or a file to a specific paragraph, or draw a highlight around the elements a reviewer should look at.

Page numbers for things, not just pages

Know on which page the invoice total, a chapter heading or an appendix ended up, to build an index, split a long document into files per section or merge the right pages into another document.

Stamps and overlays on one block only

Stamp "DRAFT" across the terms section, cover a block that must not appear in a copy or overlay a QR code or an image over a placeholder element, without touching the rest of the page.

Layout checks in automated tests

Assert that the summary fits on page one, that a table is not split where it should not be and that nothing crosses the margins; document templates get a regression test like any other code.

The documentation sample highlights every selected element with a colored border, which is the quickest way to see what the converter reports: Retrieve HTML Element Positions in PDF; the same page in the online demo runs it on your own HTML.

Step 11

Set the license key

Without a key the library runs in demo mode and stamps the output. The key you receive after purchase is set once per process, before the first conversion.

Program.csC#
using EvoPdf.Next;

// at startup, once
Licensing.LicenseKey = builder.Configuration["EvoPdf:LicenseKey"];

The HTML to PDF license covers EvoPdf Next and EvoPdf Classic: 450 USD for one application on one server, 1200 USD for unlimited developers, applications and servers with redistribution included. Details in Licensing for EvoPdf Next and on the pricing page.

More from the same converter

Everything else the HTML to PDF converter does

Each row links to the online demo page where you can try it on your own HTML and to the documentation topic with the full C# code.

Merge several HTML pages into one PDF with a header and footer across all of themDemoDocs
Convert only the elements matched by a selector, or leave some outDemoDocs
HTML forms converted to fillable PDF forms (text fields, check boxes, radio buttons, lists, buttons)DemoDocs
Internal links from the HTML kept as links in the PDFDemoDocs
Digital signature on the generated documentDemoDocs
Passwords and permissions: printing, copying, editingDemoDocs
HTML stamps and file attachments on the generated PDFDemoDocs
SVG and web fonts rendered as in the browserDemoDocs
Custom HTTP headers, cookies and POST data for the page requestDemoDocs
The same HTML to a PNG image instead of a PDFDemoDocs
EvoPdf Classic

The same conversion with the Classic library

EvoPdf Classic is the original HTML to PDF converter for .NET, in production since 2010 and still fully supported. It runs on Windows, from .NET Framework 2.0 to .NET 10, with its own rendering engine and a smaller footprint. The conversion code is the same shape; the differences are in page sizing and headers.

Classic
using EvoPdf;

var converter = new HtmlToPdfConverter();
converter.LicenseKey = "...";
converter.PdfDocumentOptions.PdfPageSize = PdfPageSize.A4;

byte[] pdf = converter.ConvertUrl(url);
using EvoPdf.Next;

Licensing.LicenseKey = "...";
var converter = new HtmlToPdfConverter();
// nothing to set: the default is an A4 page with the 1024 px layout scaled to it, as Classic did

byte[] pdf = converter.ConvertUrl(url);
Questions

Frequently asked when converting HTML to PDF in C#

Does the server need Chrome or Chromium installed?

No browser has to be installed. The rendering engine is a Chromium build shipped inside the NuGet package and integrated with the library, not a Chrome executable driven over a debugging port. On Linux a few system packages may still be missing on a bare distribution; the Getting Started on Linux guide lists them per distribution. It also works on machines without outbound internet access and on hosts where Chrome cannot run, Windows Azure App Service included.

Can I convert a Razor view or an MVC action result?

Two ways: point ConvertUrl at the action's URL, passing the user's cookies when the page requires a login, or render the view to a string in the controller and pass it to ConvertHtml with the site's base URL so that CSS and images resolve.

Will the PDF look like the page in the browser?

The engine is Chromium, so HTML5, CSS3, flexbox and grid, web fonts, SVG and JavaScript render as they do in Chrome. Print stylesheets apply when MediaType is set to print. By default the page is A4 and the HTML is laid out as in a 1024 pixel browser window, scaled to the page; LayoutAtPageWidth lays it out at the page width instead and PageWidthFromBrowserWindow makes the page as wide as the window. The settings for the usual cases are in HTML to PDF Page Setup and Scaling.

How do I run this on Azure App Service or in Docker?

App Service on Windows and Linux, Azure Functions and Linux containers are supported with the regular packages; the documentation has a step-by-step publish guide for each and the Docker page has the Dockerfiles. Windows and Linux ARM64 are covered by the .Arm64 packages.

Is .NET Framework still supported?

Yes, from 4.6.2 to 4.8.1, through the same .NET Standard 2.0 assembly, alongside .NET 6 to .NET 10.

How many conversions can run at the same time?

As many as the server can hold. Conversions run in parallel, each in its own private browser instance; one setting limits how many run at once and the memory per conversion is known in advance. Thousands of conversions in a row finish with the same footprint as the first hundred.

Next steps

Run the samples, then read the details

The online demo is the ASP.NET application you download, running on our servers; each demo page has a documentation page with its full C# source. The product page describes every feature of the converter, the packages and the deployment options.