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.
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.
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.
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.
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.
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.
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.
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
In this article we have created PDF documents with the QuestPDF library in C#.
Author
List all C# tutorials.