ZetCode

C# QuestPDF

last modified October 18, 2026

In this article we show how to create PDF documents using the QuestPDF library.

Portable Document Format (PDF) is a versatile file format created by Adobe that gives people an easy, reliable way to present and exchange documents.

QuestPDF is a .NET library for PDF generation. The document is described in C# code with a fluent API; the library computes the layout, breaks the content into pages, and renders it with Skia. The examples use QuestPDF 2026.9.

$ dotnet add package QuestPDF

We add the package to the project.

QuestPDF is free under the Community license for individuals, non-profit organizations, open-source projects, and companies with an annual gross revenue below $1M; other companies need a commercial license. The program must declare the license type before generating a document; otherwise, QuestPDF throws an exception.

C# QuestPDF simple example

The following example creates a simple PDF file.

Program.cs
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;

QuestPDF.Settings.License = LicenseType.Community;

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

    page.Content()
        .Column(x => x.Item().Text(Placeholders.Paragraph()));
}));

doc.GeneratePdf("simple.pdf");
Console.WriteLine("simple.pdf created");

The PDF document contains one paragraph of text.

QuestPDF.Settings.License = LicenseType.Community;

We declare that we use the Community license.

var doc = Document.Create(container => container.Page(page =>

The document is created with Document.Create. A page template is added with Page; the library creates as many physical pages as the content needs.

page.Size(PageSizes.A4);
page.Margin(2, Unit.Centimetre);
page.DefaultTextStyle(x => x.FontSize(12));

We set some basic properties of a page: the page size, margins, and default font size.

page.Content()
    .Column(x => x.Item().Text(Placeholders.Paragraph()));

The content is added with Content. It consists of one column, into which we add one text item. The Placeholders.Paragraph method creates a paragraph of lorem ipsum text.

doc.GeneratePdf("simple.pdf");

The PDF is generated with GeneratePdf. It can also write to a stream or return a byte array.

$ dotnet run
simple.pdf created

C# QuestPDF text

In the next example, we work with various text attributes.

Program.cs
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;

QuestPDF.Settings.License = LicenseType.Community;

var doc = Document.Create(container => container.Page(page =>
{
    page.Size(PageSizes.A4);
    page.Margin(2, Unit.Centimetre);

    page.Content()
        .Column(x =>
        {
            x.Spacing(10);

            x.Item().Text(Placeholders.Sentence());
            x.Item().Text(Placeholders.Sentence()).FontSize(15);
            x.Item().Text(Placeholders.LoremIpsum());
            x.Item().Text(Placeholders.Paragraph()).Light();
            x.Item().Text(Placeholders.Sentence()).FontColor(Colors.Blue.Darken3);

            x.Item().Text(txt =>
            {
                txt.Span("C# ").Italic().Bold();
                txt.Span("is a modern, object-oriented, and type-safe programming language.");
            });
        });
}));

doc.GeneratePdf("text.pdf");
Console.WriteLine("text.pdf created");

In the program, we utilize the Sentence, Paragraph, and LoremIpsum helper methods to generate some filler text.

x.Spacing(10);

The Spacing method sets the space between the items of the column.

x.Item().Text(Placeholders.Sentence()).FontSize(15);
...
x.Item().Text(Placeholders.Paragraph()).Light();
x.Item().Text(Placeholders.Sentence()).FontColor(Colors.Blue.Darken3);

We change the font size, the font weight, and the font colour of the text.

x.Item().Text(txt =>
{
    txt.Span("C# ").Italic().Bold();
    txt.Span("is a modern, object-oriented, and type-safe programming language.");
});

With Span, we can give different attributes to parts of a text item. The first span is bold and italic.

$ dotnet run
text.pdf created

C# QuestPDF fonts

QuestPDF uses the Lato font by default, which is bundled with the library. Other fonts are selected with FontFamily("name"); they must be installed on the system or registered with FontManager.RegisterFont. The available fonts differ between systems, so the FontManager class can list them.

Program.cs
using QuestPDF.Drawing;

var families = FontManager.GetSystemFonts()
    .Select(f => f.FamilyName)
    .Distinct()
    .Order()
    .Take(8);

Console.WriteLine(string.Join(", ", families));

var bundled = FontManager.GetRegisteredFonts()
    .Select(f => f.FamilyName)
    .Distinct();

Console.WriteLine($"bundled: {string.Join(", ", bundled)}");

We print the first eight font families installed on the system and the fonts bundled with QuestPDF.

$ dotnet run
aakar, Abyssinica SIL, Accanthis ADF Std, Accanthis ADF Std No2, Accanthis ADF Std No3, Ani, AnjaliOldLipi, Arimo
bundled: Lato

For documents that must look the same everywhere, it is best to ship the font files with the application and register them.

C# QuestPDF header & footer

For the next document, we add a header and a footer.

Program.cs
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;

QuestPDF.Settings.License = LicenseType.Community;

var doc = Document.Create(container => container.Page(page =>
{
    page.Size(PageSizes.A4);
    page.Margin(2, Unit.Centimetre);

    page.Header()
        .Text("My header")
        .SemiBold().FontSize(24).FontColor(Colors.Blue.Medium);

    page.Content()
        .PaddingVertical(1, Unit.Centimetre)
        .Column(x =>
        {
            x.Spacing(20);

            for (int i = 0; i < 12; i++)
            {
                x.Item().Text(Placeholders.Paragraph());
            }
        });

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

doc.GeneratePdf("headfoot.pdf");
Console.WriteLine("headfoot.pdf created");

The document has three parts: header, content, and footer. The content does not fit on one page; QuestPDF creates a second page and repeats the header and the footer on it.

page.Header()
    .Text("My header")
    .SemiBold().FontSize(24).FontColor(Colors.Blue.Medium);

A header is added with Header. We change the font weight with SemiBold, font size with FontSize and font colour with FontColor.

page.Content()
    .PaddingVertical(1, Unit.Centimetre)
    .Column(x =>
    {
        x.Spacing(20);

        for (int i = 0; i < 12; i++)
        {
            x.Item().Text(Placeholders.Paragraph());
        }
    });

The page content is added with Content. We add twelve paragraphs.

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

Finally, we add the footer with Footer. It contains the current page number and the total number of pages, such as "Page 2 of 2".

$ dotnet run
headfoot.pdf created

C# QuestPDF lines

In the next example, we add some horizontal lines to the page.

Program.cs
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;

QuestPDF.Settings.License = LicenseType.Community;

var doc = Document.Create(container =>
{
    container.Page(page =>
    {
        page.Size(PageSizes.A4);
        page.Margin(2, Unit.Centimetre);

        page.Content()
            .Column(x =>
            {
                x.Item().Text(Placeholders.Paragraph());
                x.Item().PaddingVertical(5).LineHorizontal(1);
                x.Item().Text(Placeholders.Paragraph());
                x.Item().PaddingVertical(5).LineHorizontal(1).LineColor(Colors.LightBlue.Darken3);
                x.Item().Text(Placeholders.Paragraph());
                x.Item().PaddingVertical(5).LineHorizontal(2);
                x.Item().Text(Placeholders.Paragraph());
            });
    });
});

doc.GeneratePdf("lines.pdf");
Console.WriteLine("lines.pdf created");

In the program, we add three lines between the paragraphs.

x.Item().PaddingVertical(5).LineHorizontal(1);

We have a horizontal line of thickness 1 with some vertical padding.

x.Item().PaddingVertical(5).LineHorizontal(1).LineColor(Colors.LightBlue.Darken3);

We can change the colour of the line with LineColor.

x.Item().PaddingVertical(5).LineHorizontal(2);

Here we make the line thicker.

$ dotnet run
lines.pdf created

C# QuestPDF borders and backgrounds

In the next example, we add borders and a background to blocks of text.

Program.cs
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;

QuestPDF.Settings.License = LicenseType.Community;

var doc = Document.Create(container => container.Page(page =>
{
    page.Size(PageSizes.A4);
    page.Margin(2, Unit.Centimetre);

    page.Content()
        .Column(x =>
        {
            x.Spacing(10);

            x.Item().Border(1).Padding(5).Text(Placeholders.Paragraph());
            x.Item().Border(1).BorderColor(Colors.Red.Medium).Padding(5).Text(Placeholders.Paragraph());
            x.Item().Background(Colors.Grey.Lighten3).Padding(5).Text(Placeholders.Paragraph());
        });
}));

doc.GeneratePdf("borders.pdf");
Console.WriteLine("borders.pdf created");

We have three paragraphs of text.

x.Item().Border(1).Padding(5).Text(Placeholders.Paragraph());
x.Item().Border(1).BorderColor(Colors.Red.Medium).Padding(5).Text(Placeholders.Paragraph());
x.Item().Background(Colors.Grey.Lighten3).Padding(5).Text(Placeholders.Paragraph());

We add a border with Border, change its colour with BorderColor, and set a background colour with Background. The Padding adds space between the border and the text. The order of the calls matters: each call wraps the elements that follow it.

$ dotnet run
borders.pdf created

C# QuestPDF table

The next example generates a table with a header row and a sum.

Program.cs
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;

QuestPDF.Settings.License = LicenseType.Community;

List<Product> products =
[
    new("Keyboard", 2, 45.90m),
    new("Mouse", 3, 19.50m),
    new("Monitor", 1, 229.00m),
    new("USB cable", 5, 4.20m),
    new("Headphones", 1, 89.99m)
];

var doc = Document.Create(container => container.Page(page =>
{
    page.Size(PageSizes.A4);
    page.Margin(2, Unit.Centimetre);

    page.Content().Table(table =>
    {
        table.ColumnsDefinition(cols =>
        {
            cols.RelativeColumn(3);
            cols.RelativeColumn();
            cols.RelativeColumn();
            cols.RelativeColumn();
        });

        table.Header(header =>
        {
            header.Cell().Element(HeaderCell).Text("Product");
            header.Cell().Element(HeaderCell).AlignRight().Text("Quantity");
            header.Cell().Element(HeaderCell).AlignRight().Text("Price");
            header.Cell().Element(HeaderCell).AlignRight().Text("Total");
        });

        foreach (var p in products)
        {
            table.Cell().Element(Cell).Text(p.Name);
            table.Cell().Element(Cell).AlignRight().Text(p.Quantity.ToString());
            table.Cell().Element(Cell).AlignRight().Text($"{p.Price:F2}");
            table.Cell().Element(Cell).AlignRight().Text($"{p.Quantity * p.Price:F2}");
        }

        decimal sum = products.Sum(p => p.Quantity * p.Price);

        table.Cell().ColumnSpan(3).Element(Cell).AlignRight().Text("Sum").Bold();
        table.Cell().Element(Cell).AlignRight().Text($"{sum:F2}").Bold();
    });
}));

doc.GeneratePdf("table.pdf");
Console.WriteLine("table.pdf created");

static IContainer HeaderCell(IContainer c) =>
    c.Background(Colors.Grey.Lighten2).Padding(5).DefaultTextStyle(x => x.SemiBold());

static IContainer Cell(IContainer c) =>
    c.BorderBottom(1).BorderColor(Colors.Grey.Lighten1).Padding(5);

record Product(string Name, int Quantity, decimal Price);

The table lists products with their quantities and prices.

table.ColumnsDefinition(cols =>
{
    cols.RelativeColumn(3);
    cols.RelativeColumn();
    cols.RelativeColumn();
    cols.RelativeColumn();
});

We define four columns. Relative columns share the available width; the first column is three times wider than the others. The ConstantColumn method defines a column of a fixed width.

table.Header(header =>
{
    header.Cell().Element(HeaderCell).Text("Product");
    ...
});

The header row is repeated on each page if the table spans several pages.

static IContainer Cell(IContainer c) =>
    c.BorderBottom(1).BorderColor(Colors.Grey.Lighten1).Padding(5);

The cell styles are defined in static methods and applied with Element, so we do not repeat them for every cell.

table.Cell().ColumnSpan(3).Element(Cell).AlignRight().Text("Sum").Bold();

The ColumnSpan method makes the cell span three columns.

$ dotnet run
table.pdf created

The table has a grey header row, right-aligned numbers, and a bold sum of 490.29 in the last row.

Source

QuestPDF documentation

In this article we have created PDF documents with the QuestPDF library in C#.

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all C# tutorials.