PDFsharp Guide: Create PDF Files with MigraDoc and PdfSharpCore
PDFsharp is an open source .NET library for creating and editing PDF documents from C# code. You draw text, lines and images onto a page, call Save, and you have a PDF file. No browser engine, no native binary. The project dates back to 2005, and a lot of the invoices, reports and certificates coming out of .NET back ends today still pass through it.
I wrote this for developers who need PDFs on the server and want to know what they're getting into. We'll cover how PDFsharp and MigraDoc fit together, which build to install, a first working file, the font headache on Linux, the recent license change, and the point where you should pick something else.
What Is PDFsharp and What Can It Do?
Behind the PDFsharp library sits empira Software GmbH, a small German shop that has kept the thing alive for close to twenty years. Most of your time goes into one class, XGraphics. If you ever did WinForms painting with System.Drawing.Graphics, you'll feel at home within ten minutes: font, brush, x and y, go.
Out of the box you get:
- Create PDF documents from scratch with text, vector shapes, lines, and images
- Open existing PDF files and add content to their pages, such as stamps, watermarks, or page numbers
- Merge and split documents by importing pages from one file into another
- Embed and subset fonts so the file looks the same on every machine
- Add bookmarks, links, and document metadata like title, author, and keywords
- Protect files with passwords and permission flags
- Sign documents digitally (since the 6.2 release line)
Deployment is dull, which I mean as praise. Everything is managed code, so your ops person never has to put headless Chrome on a box or hunt for a Ghostscript DLL at 2 a.m. You reference one NuGet package, push, done.
Now the catch. You get a canvas and nothing more. Long paragraph hits the bottom margin? Your problem. Table runs past page one? Also yours, unless you bring in MigraDoc.
PDFsharp and MigraDoc: Which Layer Do You Need?
Same vendor, same release schedule, very different jobs. MigraDoc Foundation lives one floor up from PDFsharp. Instead of telling it where a string goes, you hand it sections, paragraphs, tables, headers, footers, styles and the odd chart, and it figures out where everything lands. Out comes a PDF. Or RTF, for the colleague who insists on opening everything in Word.
My shorthand: PDFsharp is "put this text at x=40, y=120." MigraDoc is "here's thirty rows and a header that repeats, you sort out the pages."
| Task | Best fit | Why |
|---|---|---|
| One-page certificate or ticket with fixed positions | PDFsharp | Full control over every coordinate |
| Multi-page report with tables and running headers | MigraDoc | Automatic pagination and styles |
| Invoice with a variable number of line items | MigraDoc | Tables grow and break across pages |
| Stamp, watermark, or page numbers on existing PDF | PDFsharp | MigraDoc only creates new documents |
| Merge or split existing files | PDFsharp | Page import is a PDFsharp feature |
| Business chart inside a report | MigraDoc | Built-in chart support |
| Mixed: generated report plus a custom-drawn cover | Both | MigraDoc can render onto a PDFsharp page |
Real projects tend to end up with both. Body text through MigraDoc, a hand-drawn cover or a stamp on some vendor's PDF through the lower layer. You can't really avoid installing PDFsharp anyway, because the MigraDoc package drags it in as a dependency.
PDFsharp Builds Explained: Core Build, GDI+ and WPF
Newcomers usually get stuck next on the package list in NuGet. PDFsharp 6 comes in three builds, each with its own MigraDoc twin.
| NuGet package | Build | Platforms | Fonts and images |
|---|---|---|---|
| PDFsharp / PDFsharp-MigraDoc | Core | Windows, Linux, macOS | Needs a font resolver; BMP, PNG, JPEG images |
| PDFsharp-GDI / PDFsharp-MigraDoc-GDI | GDI+ | Windows only | Uses installed fonts via System.Drawing |
| PDFsharp-WPF / PDFsharp-MigraDoc-WPF | WPF | Windows only | Uses installed fonts via WPF |
Current packages target .NET 8, 9 and 10, plus .NET Standard 2.0 and .NET Framework 4.6.2. So pretty much anything still being worked on is covered, from a dusty WinForms tool to a brand-new ASP.NET Core API.
Console app, web API, Docker, Azure Functions, anything on Linux? Core build. GDI+ or WPF only make sense for a Windows desktop app where you want system fonts or a preview in a native control. Someone on Stack Overflow once asked if their console app needed the WPF version. It didn't.

How to Install PDFsharp From NuGet in Visual Studio
You'll still find forum posts from the WinForms era telling you to grab a ZIP, fish out a DLL and add the reference yourself. Ignore them. Install PDFsharp through NuGet; the package manager tracks versions so you don't have to.
- Open your solution in Visual Studio and right-click the project in Solution Explorer.
- Choose Manage NuGet Packages, switch to the Browse tab, and search for "PDFsharp".
- Select PDFsharp (Core build) or PDFsharp-MigraDoc if you also need document layout. Check the publisher is empira Software GmbH.
- Click Install and accept the license prompt.
- Build the project once to confirm the package restored correctly.
Terminal people can skip the clicking:
bash dotnet add package PDFsharp dotnet add package PDFsharp-MigraDoc
Inside the Package Manager Console the same thing is Install-Package PDFsharp. And please don't mix builds. I've watched a team lose a whole afternoon to type conflicts because one project referenced PDFsharp and its neighbour pulled in PDFsharp-GDI.
Your First PDF Document With PDFsharp in C
Below is about the smallest program that produces a one-page PDF file. It runs on the Core build on any OS, as long as a font resolver is set up (next section).
```csharp
using PdfSharp.Drawing;
using PdfSharp.Pdf;
// PDFsharp 6.5+ requires choosing a license before first use
PdfSharpLicense.SetAgplLicense();
var document = new PdfDocument();
document.Info.Title = "Hello PDFsharp";
PdfPage page = document.AddPage();
XGraphics gfx = XGraphics.FromPdfPage(page);
var font = new XFont("Arial", 20, XFontStyleEx.Bold);
gfx.DrawString("Hello, PDFsharp!", font, XBrushes.Black,
new XRect(0, 0, page.Width.Point, page.Height.Point),
XStringFormats.Center);
document.Save("hello.pdf");
```
Every PDFsharp program follows this shape: document, page, XGraphics, draw, save. Units are points (1/72 inch) and the origin sits in the top-left corner. On 6.2.x, delete the license line. That API only showed up in 6.5.0.
And the MigraDoc version:
```csharp
using MigraDoc.DocumentObjectModel;
using MigraDoc.Rendering;
var doc = new Document();
Section section = doc.AddSection();
section.AddParagraph("Hello, MigraDoc!");
var renderer = new PdfDocumentRenderer { Document = doc };
renderer.RenderDocument();
renderer.PdfDocument.Save("hello-migradoc.pdf");
```
No coordinates anywhere. Throw 500 paragraphs at it and you get however many pages that takes, with zero pagination code on your side.
Fonts in the PDFsharp Core Build: The Resolver Problem
Ask around and fonts are the complaint you'll hear most. GDI+ and WPF builds just read installed TrueType fonts from Windows. The Core build can't assume anything, because a slim Linux container often has no usable fonts at all. So the first Docker deploy tends to end with an exception about no appropriate font being found.
The fix is a font resolver, a class that implements IFontResolver. It has two methods:
- ResolveTypeface maps a requested family and style (say, "Arial", bold) to a face name
- GetFont returns the raw bytes of the TTF file for that face name
Register it once at startup via GlobalFontSettings.FontResolver, before the first XFont gets created.
```csharp
public class AppFontResolver : IFontResolver
{
public FontResolverInfo? ResolveTypeface(string family, bool bold, bool italic)
=> new FontResolverInfo(bold ? "Inter-Bold" : "Inter-Regular");
public byte[]? GetFont(string faceName) => File.ReadAllBytes(Path.Combine(AppContext.BaseDirectory, "Fonts", faceName + ".ttf"));
}
GlobalFontSettings.FontResolver = new AppFontResolver();
```
Things worth knowing before you hit them yourself:
- Keep your TTF files in a Fonts folder in the project and set them to copy to the output directory, or embed them as resources so the container image always has them.
- Release 6.2.0 removed the old CoreBuildFontResolver. On Windows or WSL2 you can restore similar behavior with GlobalFontSettings.UseWindowsFontsUnderWindows, but this does nothing on Linux or macOS.
- The official samples repository includes a SubstitutingFontResolver you can copy as a starting point.
- Check the font license before embedding a commercial typeface in generated files. Open fonts like Inter, Roboto, or Noto avoid the question entirely.
With the resolver in place, output looks identical on Windows, Linux and macOS.
Merging, Signing and Securing Files with PDFsharp 6
Plenty of teams never generate a document from scratch. They use the library to glue, stamp or lock files that already exist. Merging is a few lines: open each source in import mode and copy the pages over.
csharp var output = new PdfDocument(); foreach (var path in new[] { "part1.pdf", "part2.pdf" }) { using var input = PdfReader.Open(path, PdfDocumentOpenMode.Import); foreach (PdfPage p in input.Pages) output.AddPage(p); } output.Save("merged.pdf");
Splitting is the same loop backwards, one output file per page or range. For protection, document.SecuritySettings takes a user password, an owner password and permission flags (print, copy text and so on).
Two long-requested features finally landed in the 6.2 line:
- Digital signatures based on CMS. A default signer ships in a separate PDFsharp.Cryptography assembly, and you can plug in your own by implementing IDigitalSigner. Timestamp server support works on modern .NET but not on the .NET Framework build.
- PDF/A output for archiving. The official docs describe it as working "with some limitations" and still under construction, so validate the result with a PDF/A checker before relying on it for compliance.
That release also brought color emoji glyphs and a managed MD5 implementation, which matters if you run under FIPS rules or in Blazor.
PDFsharp Repository, PdfSharpCore Forks and 1.x Versions
Search GitHub for PDFsharp and you'll find a handful of projects with almost the same name. Blame history.
The old 1.x line (1.50, 1.51) only ran on .NET Framework. When .NET Core came out, people didn't feel like waiting, so they ported it themselves. PdfSharpCore, started by Stefan Steiger and picked up by other forks later, is the one most people know. It ripped out GDI+ and got the code running on Linux years ahead of the official release.
Then PDFsharp 6 arrived: a proper rewrite on modern .NET with an official cross-platform Core build. The forks lost most of their reason to exist.
| Project | Maintainer | Platforms | Status |
|---|---|---|---|
| PDFsharp 6.x (empira/PDFsharp repository) | empira Software GmbH | .NET 8–10, .NET Standard 2.0, .NET Framework 4.6.2 | Actively maintained |
| PDFsharp 1.5x | empira Software GmbH | .NET Framework only | Source code kept on GitHub, no longer maintained |
| PdfSharpCore and its forks | Community | .NET Core / .NET Standard 2.0 | Varies by fork, slower updates |
| PDFsharp-MigraDoc 6.x | empira Software GmbH | Same as PDFsharp 6 | Actively maintained |
So, the "PdfSharp vs PdfSharpCore" thread that keeps showing up on Reddit? New project, official packages. Keep a fork only if old code already leans on it and migrating isn't worth a sprint right now. And when the docs feel thin, go to empira's separate samples repository. Its folders and files are runnable examples, which beats guessing.
Is PDFsharp Free? Licensing Changes in PDFsharp 6.5
For most of its life, yes. It was MIT licensed, which means commercial use with almost no strings attached. Every release up to and including 6.2.4 still is.
6.5.0 changed that. The library is now dual-licensed:
- AGPL (free). You enable it by calling PdfSharpLicense.SetAgplLicense() before using the library. The AGPL requires you to make your source code available to users, including users who interact with your software over a network, so it is a poor match for closed-source SaaS.
- Commercial license. You buy a license key from the vendor and load it with PdfSharpLicense.LoadLicense(...). This removes the AGPL obligations.
From 6.5.0 on, your code has to pick one explicitly. The 7.0 previews work the same way.
In practice:
- Open source projects compatible with AGPL can keep using the newest version for free.
- Closed-source products can either buy a commercial license or stay on 6.2.x, which remains under MIT. Staying put means missing future fixes, so weigh that cost honestly.
- Whatever you choose, read the actual license text and get your legal team to sign off. This guide is not legal advice.

Where PDFsharp Falls Short and What to Pair It With
The library does its job well. The job is just narrow, and the same gaps come up in every forum thread.
- No HTML to PDF. PDFsharp does not render HTML or CSS. A community add-on, HtmlRenderer.PdfSharp, exists but supports only HTML 4.01 and CSS level 2, so modern layouts with flexbox or grid will not survive. Teams that need HTML templates usually reach for a headless-browser renderer instead.
- No JavaScript. Charts or content built on the client side cannot be captured.
- No rendering to images. PDFsharp cannot turn a PDF page into a PNG for thumbnails or previews. You need a separate rasterizer for that.
- Limited text extraction. You can walk the content stream, but there is no high-level API that returns clean text with positions. PdfPig, a separate open-source library, is built specifically for reading and extracting text.
- Partial form support. Simple AcroForm filling works; complex forms with scripts are another story.
- Tables need MigraDoc. With plain PDFsharp, you calculate every row height yourself.
That doesn't make it a bad pick. Data-driven documents like invoices, statements, labels and certificates are exactly its sweet spot, and the footprint is tiny. Pixel-perfect copies of web pages are not.
PDFsharp for Crypto Invoices and Payment Receipts
Receipts are probably the most common thing people build with PDFsharp. Crypto receipts are a little fussier than card ones. The good news: once the gateway hits your webhook (an HTTP callback sent when the payment status flips), you've got all the data. Nothing extra to look up.
What I'd put on one:
- Order or invoice ID and the merchant's details
- Amount in cryptocurrency and its fiat equivalent at the moment of payment
- Coin and network (for example, USDT on TRON versus USDT on Ethereum)
- Transaction hash, so the customer can verify it on a block explorer
- A shortened receiving wallet address and the confirmation time
- A QR code linking to the transaction or invoice page
Monospace for hashes and addresses. Customers squint at them one character at a time, and in Arial a zero and a capital O look like twins. If the hash is too wide, wrap it; 6pt text helps nobody. Embed fonts, save the file with the order, and sign it if your accountant ever asks for receipts.
None of that covers actually taking the money. For that piece you'd plug in a gateway like Plisio. It takes BTC, USDT and plenty of other coins, makes the invoice, and calls your endpoint each time the status changes. Your handler passes the payload to MigraDoc and the buyer gets a receipt with your logo on it while they're still on the thank-you page.
Conclusion
I keep coming back to PDFsharp for one reason: it's small and it stays out of the way. The Core build runs anywhere .NET does, and MigraDoc picks up the layout work when a canvas isn't enough. Three things to settle early: which build, how fonts get loaded, and which license you're on now that 6.5 went AGPL. Print data, not web pages, and it'll hold up for years.