All formats

QuestPDF

LanguageC# (.NET 6+, .NET Framework 4.6.2+ via .NET Standard 2.0)
LicenseQuestPDF Community MIT license (free for companies under $1M annual revenue, individuals, non-profits and open source), otherwise commercial
Repositorygithub.com/QuestPDF/QuestPDF
Documentationquestpdf.com
Depends onbundled native Skia (rendering) and qpdf (document operations) libraries for Windows, Linux and macOS
Read / writeno (except document operations on existing files) / yes

QuestPDF builds documents from C# code with a layout engine: you describe pages as nested containers (columns, rows, tables, padding, borders, images, text spans) and QuestPDF breaks content across pages, repeats headers and footers, and numbers pages. It’s the most productive way to generate invoices, reports and certificates in .NET. The Companion app shows a live preview while you edit the code.

Since version 2024.12 the document operations API (built on qpdf) merges, splits, overlays, encrypts and decrypts existing PDF files. QuestPDF doesn’t extract text or render existing PDFs.

The license isn’t OSI-approved: the code is public, but companies with $1M or more annual gross revenue need a paid Professional or Enterprise license. You must declare the license in code.

Installation#

Shell
dotnet add package QuestPDF

Basic usage: create a PDF with text and a table#

C#
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;

QuestPDF.Settings.License = LicenseType.Community;

var items = new[] { ("Widget", 2, 9.99m), ("Gadget", 1, 24.50m) };

Document.Create(container =>
{
    container.Page(page =>
    {
        page.Size(PageSizes.A4);
        page.Margin(2, Unit.Centimetre);
        page.DefaultTextStyle(x => x.FontSize(11));

        page.Header().Text("Invoice #1001").SemiBold().FontSize(24);

        page.Content().PaddingVertical(1, Unit.Centimetre).Table(table =>
        {
            table.ColumnsDefinition(columns =>
            {
                columns.RelativeColumn(3);
                columns.RelativeColumn();
                columns.RelativeColumn();
            });
            table.Header(header =>
            {
                header.Cell().Text("Item").Bold();
                header.Cell().AlignRight().Text("Qty").Bold();
                header.Cell().AlignRight().Text("Price").Bold();
            });
            foreach (var (name, qty, price) in items)
            {
                table.Cell().Text(name);
                table.Cell().AlignRight().Text(qty.ToString());
                table.Cell().AlignRight().Text($"{price:F2}");
            }
        });

        page.Footer().AlignCenter().Text(x =>
        {
            x.Span("Page ");
            x.CurrentPageNumber();
            x.Span(" of ");
            x.TotalPages();
        });
    });
})
.GeneratePdf("invoice.pdf");

GeneratePdf() without arguments returns a byte[]; GeneratePdf(stream) writes to a stream.

How to set PDF metadata#

C#
Document.Create(container => { /* pages */ })
    .WithMetadata(new DocumentMetadata
    {
        Title = "Invoice #1001",
        Author = "Billing service",
        Subject = "Invoice",
    })
    .GeneratePdf("invoice.pdf");

How to merge PDF files#

C#
using QuestPDF.Fluent;

DocumentOperation
    .LoadFile("a.pdf")
    .MergeFile("b.pdf")
    .MergeFile("c.pdf", "1-3")  // only pages 1-3 of c.pdf
    .Save("merged.pdf");

How to extract pages from a PDF#

Page selections use qpdf syntax: 1,3,5-10, z for the last page, r1 for the last page counted from the end:

C#
DocumentOperation
    .LoadFile("in.pdf")
    .TakePages("1-5")
    .Save("first-five.pdf");

How to encrypt and decrypt a PDF#

C#
DocumentOperation
    .LoadFile("in.pdf")
    .Encrypt(new DocumentOperation.Encryption256Bit
    {
        UserPassword = "user",
        OwnerPassword = "owner",
        AllowPrinting = true,
        AllowContentExtraction = false,
    })
    .Save("encrypted.pdf");

DocumentOperation
    .LoadFile("encrypted.pdf", "owner")
    .Decrypt()
    .Save("decrypted.pdf");

Tips#