PeachPDF: a pure .NET HTML to PDF renderer with no browser process
Peach PDF is a pure .NET HTML -> PDF rendering library
At a glance
- What is it?
- PeachPDF renders HTML to PDF entirely in .NET, with no Puppeteer, wkhtmltopdf or any other external renderer, which means it runs wherever .NET 8 does and can also run under WebAssembly in a browser. Its feature set covers vector SVG, MathML, Paged Media, PDF/UA, PDF/A, AES-256 encryption and a code-first document API, and the recent releases are frequent patch updates.
- Who is it for?
- PeachPDF fits a .NET team that has to produce PDFs server side without shipping a browser, and it fits browser and WebAssembly deployments through the same library, which the hosted demo demonstrates with nothing uploaded anywhere. It does not fit a team whose HTML depends on browser behaviour the compatibility matrix does not cover, since the feature list is a curated subset of CSS, SVG and MathML rather than a browser engine.
- Can I use it commercially?
- Yes. BSD-3-Clause is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository received new commits within the last day.
- What is it written in?
- Mainly C#, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Why not just run a browser to make the PDF
The usual way to turn HTML into a PDF on a server is to drive a headless browser. Puppeteer, wkhtmltopdf and their equivalents all mean shipping or depending on a browser, and that dependency is where the operational pain comes from.
PeachPDF is a pure .NET HTML to PDF rendering library, and the project states that it does not depend on Puppeteer, wkhtmltopdf, or any other process to render the HTML to PDF. The stated consequence is that it should work in virtually any environment where .NET 8 works.
There is a second argument, and it is the more interesting one for a library that will live inside somebody else's product. As a side benefit of being pure .NET, performance improvements in future .NET versions immediately benefit this library. A library with no browser process has no browser to upgrade, no Chromium to patch, and no memory profile of a full rendering engine running per conversion.
There is also a deployment option that browser based approaches cannot easily offer. The project hosts a browser demo where the same library runs on WebAssembly, rendering your own HTML, MHTML or ZIP with nothing uploaded anywhere. That is a meaningful data protection property: a document never leaves the browser tab.
The users are .NET teams producing documents server side, invoice and report generators, and anyone who wants HTML to PDF in the browser without a round trip to a rendering service.
The CSS, SVG and MathML coverage is a curated subset, not a browser
The feature list is long, and it is worth reading it as a specification of what the renderer implements rather than as a wish list.
On CSS there is real depth: custom properties and var() including fallbacks and inheritance, the math functions calc(), min(), max() and clamp(), 2D and 3D transforms including translate, scale, rotate, skew, matrix and their variants, Flexbox Level 1, multi column layout, all five CSS-wide keywords from inherit through to revert-layer, and gradients including linear, radial, conic and repeating variants with CSS Color Level 4 interpolation.
CSS Paged Media is the part that matters most for documents. It supports @page rules, named pages, margin boxes, and running headers and footers through string-set and string() with position: running() and element(). That is print layout, not screen layout approximated.
SVG is rendered as native vectors, for inline svg, for standalone img src with an .svg file or a data:image/svg+xml URI, and as a background-image or list-style-image source. The single exception is an SVG filter that uses a blur, a lighting effect or another pixel primitive, which is rendered as a high-resolution bitmap. MathML is likewise vector, targeting the MathML Core layout algorithm, with fraction bars, radicals and stretchy operators built from the font's own OpenType MATH table and never rasterised.
The project publishes three compatibility matrices, for HTML and CSS, for SVG features and for MathML features. That is the honest framing of what this is: a renderer that covers a lot of the modern web, and where it does not cover something, says so in a table rather than rendering it wrong.
PDF output, accessibility and archival formats
The output is PDF 2.0, the ISO 32000-2 standard, by default, with PDF 1.x available through PdfGenerateConfig.PdfVersion.
On accessibility, the project offers optional Tagged PDF output under PDF/UA, which gives a logical structure tree, automatic document language and CSS driven tag mapping through a -peachpdf-pdf-tag-type property, controlled by PdfGenerateConfig.EnableTaggedPdf. MathML formulas are tagged as accessible Formula structure elements, carrying their original MathML source as a PDF 2.0 Associated File, so the source survives into the document rather than only the rendering.
Archival conformance is optional and configured with PdfGenerateConfig.PdfAConformance, covering parts 1, 2 and 3 of ISO 19005 with the visual-only B, guaranteed-text-extraction U and accessible A levels. That includes file attachments for PDF/A-3 through PdfGenerateConfig.Attachments, and ZUGFeRD and Factur-X hybrid e-invoices through PdfGenerateConfig.FacturX, which is the option that matters if you are shipping a compliant invoice rather than a brochure.
Encryption is optional AES-256 with user and owner passwords and permissions such as print, copy and read-only, and the project is explicit that it uses only the .NET cryptography stack, with the setting under PdfGenerateConfig.Encryption.
Two conveniences come for free. PDF metadata is extracted automatically from the HTML title and meta elements. And a PDF outline, the bookmark sidebar, is generated from headings with no configuration required, with CSS control through bookmark-level, bookmark-label and bookmark-state from the CSS Generated Content Module Level 3.
The code-first document API and the network loaders
There are two ways to use PeachPDF, and the second one is unusual.
The first is HTML in, PDF out. The simple example configures a PdfGenerateConfig with a page size and orientation, creates a PdfGenerator, and calls GeneratePdf with the HTML and the config, then saves the document to a stream:
PdfGenerateConfig pdfConfig = new(){
PageSize = PageSize.Letter,
PageOrientation = PageOrientation.Portrait
};
PdfGenerator generator = new();
var stream = new MemoryStream();
var document = await generator.GeneratePdf(html, pdfConfig);
document.Save(stream);The HTML can come from a file, from a data URI, or through a network loader. For self contained MHTML files, the single page document format that Chrome calls that, the project includes a MimeKitNetworkLoader, and the loader instance is passed in the config. For HTML from the internet there is an HttpClientNetworkLoader, which takes an HttpClient and a base Uri, and passing null as the HTML makes GeneratePdf load it from the loader instead.
The second way is a declarative, code-first document building API through PdfGenerator.CreateDocument. You build pages, containers, text, images, rows and columns, tables, lists, repeating headers and footers with page numbers, a document level stylesheet, and HTML fragments with slots, directly in C#, with no HTML or CSS strings required.
That second API is the interesting one for a .NET team, because it means a report can be a strongly typed object graph rather than a template string that has to be escaped correctly, and it means the same rendering engine backs both paths.
Fonts, and the pixel change that will break your layout
Font handling is where HTML to PDF renderers usually fall short, and the project goes into detail. There are web fonts through @font-face, custom fonts loaded from a stream, and system font discovery, plus per character font matching using @font-face unicode-range and coverage based fallback across the font-family stack. Supplementary plane text is handled through cmap format 12, and colour emoji using COLR and CPAL are rendered as native PDF vectors and remain searchable and selectable.
The breaking change in the current release line is the one to plan for. CSS pixels now resolve at the CSS specified physical ratio, where 1px equals 1/96in equals 0.75pt, everywhere: layout, borders, images and @page geometry, matching browser print output. Earlier versions treated 1px as 1pt for non font lengths, which rendered px sized content 33 percent larger than its true CSS size. Upgrading therefore shrinks px derived lengths by a factor of 0.75.
The exceptions are stated too. Absolute units such as pt, mm, cm, in and pc are unaffected, and px font sizes are unaffected because they already used the correct ratio.
That change is the right call, because matching browser print output is what a user checks a PDF against. But it means any template tuned against the old behaviour will reflow, and a page that used to fit will no longer fit. If you are upgrading an existing deployment, this is the item to test first.
Where PeachPDF is the wrong tool
The first limit is that a curated renderer is not a browser. PeachPDF implements the parts of CSS, SVG and MathML listed in its feature set, and the compatibility matrices are the contract. Anything outside that set, a JavaScript-driven layout, a canvas-heavy dashboard, a page that depends on runtime script execution, will not render the way a browser renders it. The project is candid about this by publishing matrices, and the honest expectation is that you check your template against them rather than assume.
The second limit is the .NET requirement. The project requires .NET 8 or newer, every currently supported .NET version according to Microsoft's own support policy. That is a floor and a ceiling in one statement, so a team on an older runtime, or one targeting a platform where modern .NET does not go, is out of luck, and the claim that it works in virtually any environment where .NET 8 works is precisely scoped to the .NET ecosystem.
The third is the embedded engine licence. The package embeds a PDF writing engine derived from PDFsharp directly in its source tree, and the project notes that this engine carries its own licence, pointing at src/PeachDrawing.PDF/Engine/LICENSE.md, while stating that the end result is still open source. Portions of the software are also copyright the FreeType Project. For a corporate legal review, that means the package you install is not a single BSD-3-Clause artefact, and the third party licence file at the root of the repository, THIRD-PARTY-LICENSES.md, is the starting point.
Compare that with the browser based approach. A headless browser will render essentially any page correctly, including the parts PeachPDF does not implement, at the cost of shipping a browser, a sandbox, a process per conversion and a memory profile you have to plan for. The difference in approach is that PeachPDF trades coverage for operational simplicity and browser grade output on the features it does implement, which is the right trade when your documents are generated from a template you control, and the wrong one when they are arbitrary web pages.
Licence, release cadence and the shape of the repository
PeachPDF is released under the BSD-3-Clause licence, with the text in the LICENSE file at the repository root. The BSD 3 clause terms are permissive, and for a library that a commercial .NET product will call, that is the right kind of licence, since there is no copyleft obligation on your own code. The caveat is the embedded engine described above, and the FreeType attribution, so the legal review is not a one line answer.
The release cadence is fast and the version numbers show it. v0.9.21 was published on 2026-10-02, v0.9.22 on 2026-10-09 and v0.9.23 on 2026-10-16, so roughly weekly patch releases. The repository is not archived and the last push was on 2026-10-20, so the tree is a few days old.
A 0.9.x line with weekly patches means the API surface is still moving, and the breaking change to pixel resolution shows that a release can change rendering output. Pin the exact version, and when you upgrade, diff the rendered PDFs rather than trusting the changelog.
The repository structure backs the product. There is a src/ directory for the library, a tools/ directory, a docs/ directory that feeds the compatibility matrices published on the site, assets/, a global.json pinning the SDK, a .vscode/ directory, and the project meta files including CONTRIBUTING.md, CLAUDE.md and THIRD-PARTY-LICENSES.md.
What to verify before you build on it is specific. Take a representative template, run it through the hosted browser demo to see the rendering without installing anything, then check each feature it uses against the HTML and CSS support matrix, and confirm the font stack you rely on is one the project can match, since a missing font changes both metrics and appearance in a PDF that nobody can diff automatically.
Editorial conclusion
PeachPDF fits a .NET team that has to produce PDFs server side without shipping a browser, and it fits browser and WebAssembly deployments through the same library, which the hosted demo demonstrates with nothing uploaded anywhere. It does not fit a team whose HTML depends on browser behaviour the compatibility matrix does not cover, since the feature list is a curated subset of CSS, SVG and MathML rather than a browser engine. Before you adopt it, read the HTML and CSS support matrix for the specific features your templates use, check the PDF/A and PDF/UA levels you need against PdfGenerateConfig, and read the embedded PDF engine licence at src/PeachDrawing.PDF/Engine/LICENSE.md, because that licence travels with the package.
Frequently asked questions
Does PeachPDF need a browser to render HTML to PDF?
No. PeachPDF is a pure .NET HTML to PDF rendering library and does not depend on Puppeteer, wkhtmltopdf or any other process to render the HTML to PDF, so it works wherever .NET 8 works. The same library also runs on WebAssembly in the browser demo.
How do I install PeachPDF and generate a first PDF?
Add the package with dotnet add package PeachPDF, create a PdfGenerateConfig with a page size and orientation, instantiate a PdfGenerator, then await generator.GeneratePdf(html, pdfConfig) and save the document to a stream.
What PDF formats can PeachPDF produce?
PDF 2.0 by default with PDF 1.x through PdfGenerateConfig.PdfVersion, optional Tagged PDF under PDF/UA via PdfGenerateConfig.EnableTaggedPdf, optional PDF/A conformance for parts 1, 2 and 3 via PdfGenerateConfig.PdfAConformance including PDF/A-3 attachments and ZUGFeRD or Factur-X hybrid e-invoices, and optional AES-256 encryption via PdfGenerateConfig.Encryption.
What changed about CSS pixel sizes in PeachPDF?
px lengths now resolve at the CSS specified physical ratio of 1px equals 1/96in equals 0.75pt everywhere, matching browser print output, where earlier versions treated 1px as 1pt for non font lengths. Upgrading shrinks px derived lengths by a factor of 0.75, while absolute units and px font sizes are unaffected.
Can PeachPDF render HTML from a URI or an MHTML file?
Yes. Self contained MHTML files are loaded with the included MimeKitNetworkLoader, and HTML from the internet is loaded with HttpClientNetworkLoader, which takes an HttpClient and a base Uri. Passing null as the HTML makes GeneratePdf load the content from the configured loader.
What licence does PeachPDF use?
PeachPDF is released under the BSD-3-Clause licence, with the terms in the LICENSE file at the repository root. The package also embeds a PDF writing engine derived from PDFsharp which carries its own licence at src/PeachDrawing.PDF/Engine/LICENSE.md, and portions are copyright the FreeType Project.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/jhaygood86-peachpdf)Was this useful?