
1. About QuestPDF
QuestPDF is an open-source library for generating PDF documents in .NET or .NET Core.
It provides a layout engine designed with full page break support and flexibility in mind. It is much easier to use than common libraries like Aspose and iTextSharp!
- GitHub: https://github.com/QuestPDF/QuestPDF
- Official website: https://www.questpdf.com/

2. Installation
Install-Package QuestPDF

3. Examples
3.1 Simple Example
Generating a PDF document consists of three parts: Header, Content, and Footer. Below is the example code:
Document.Create(container =>
{
container.Page(page =>
{
page.Size(PageSizes.A4);
page.Margin(2, Unit.Centimetre);
page.Background(Colors.White);
page.DefaultTextStyle(x => x.FontSize(20));
page.Header()
.Text("Hello PDF!")
.SemiBold().FontSize(36).FontColor(Colors.Blue.Medium);
page.Content()
.PaddingVertical(1, Unit.Centimetre)
.Column(x =>
{
x.Spacing(20);
x.Item().Text(Placeholders.LoremIpsum());
x.Item().Image(Placeholders.Image(200, 100));
});
page.Footer()
.AlignCenter()
.Text(x =>
{
x.Span("Page ");
x.CurrentPageNumber();
});
});
})
.GeneratePdf("hello.pdf");
The resulting PDF from the above code:

3.2 Template Generation
Using template generation involves three application layers:
- Document Model (a set of classes describing the PDF content)
- Data Source (the layer mapping domain entities to the document model)
- Template (the presentation layer describing how to visualize information and convert it into a PDF file)
For example, let's design a basic invoice with a shopping list, seller and buyer addresses, invoice number, etc. We'll design three model classes:
public class InvoiceModel
{
public int InvoiceNumber { get; set; }
public DateTime IssueDate { get; set; }
public DateTime DueDate { get; set; }
public Address SellerAddress { get; set; }
public Address CustomerAddress { get; set; }
public List<OrderItem> Items { get; set; }
public string Comments { get; set; }
}
public class OrderItem
{
public string Name { get; set; }
public decimal Price { get; set; }
public int Quantity { get; set; }
}
public class Address
{
public string CompanyName { get; set; }
public string Street { get; set; }
public string City { get; set; }
public string State { get; set; }
public object Email { get; set; }
public string Phone { get; set; }
}
After defining the models, we define some dummy data to fill the PDF:
public static class InvoiceDocumentDataSource
{
private static Random Random = new Random();
public static InvoiceModel GetInvoiceDetails()
{
var items = Enumerable
.Range(1, 8)
.Select(i => GenerateRandomOrderItem())
.ToList();
return new InvoiceModel
{
InvoiceNumber = Random.Next(1_000, 10_000),
IssueDate = DateTime.Now,
DueDate = DateTime.Now + TimeSpan.FromDays(14),
SellerAddress = GenerateRandomAddress(),
CustomerAddress = GenerateRandomAddress(),
Items = items,
Comments = "Test comments"
};
}
private static OrderItem GenerateRandomOrderItem()
{
return new OrderItem
{
Name = "Product",
Price = (decimal)Math.Round(Random.NextDouble() * 100, 2),
Quantity = Random.Next(1, 10)
};
}
private static Address GenerateRandomAddress()
{
return new Address
{
CompanyName = "Test Store",
Street = "Test Street",
City = "Test City",
State = "Test State",
Email = "test@example.com",
Phone = "123-456-7890"
};
}
}
Then we build the template scaffolding. To use the template scaffolding, we start by defining a new class that implements the IDocument interface.
The interface contains two methods:
DocumentMetadata GetMetadata();void Compose(IDocumentContainer container);
The first returns basic metadata for the template document, and the second defines the template container. Based on these principles, we design a template layer class:
public class InvoiceDocument : IDocument
{
public InvoiceModel Model { get; }
public InvoiceDocument(InvoiceModel model)
{
Model = model;
}
public DocumentMetadata GetMetadata() => DocumentMetadata.Default;
public void Compose(IDocumentContainer container)
{
container
.Page(page =>
{
page.PageColor(Colors.Red.Lighten1);
page.Size(PageSizes.A4);
page.Margin(10); // margin
page.Header().Height(100).Background(Colors.LightBlue.Lighten1);
page.Content().Background(Colors.Grey.Lighten3);
page.Footer().Height(50).Background(Colors.Grey.Lighten1);
});
}
}
A PDF page always has three elements: header, footer, and content. Let's check the generated document:

So far, we have built a very simple page where each section has a different color or size.
Next, we fill in the header. After organizing the data source, we can call the Element method to compose:
public void Compose(IDocumentContainer container)
{
container
.Page(page =>
{
page.PageColor(Colors.Red.Lighten1);
page.Size(PageSizes.A4);
page.Margin(10); // margin
page.Header().Height(100).Background(Colors.LightBlue.Lighten1).Element(ComposeHeader);
page.Content().Background(Colors.Grey.Lighten3);
page.Footer().Height(50).Background(Colors.Grey.Lighten1);
});
}
void ComposeHeader(IContainer container)
{
var titleStyle = TextStyle.Default.FontSize(20).SemiBold().FontColor(Colors.Blue.Medium);
container.Row(row =>
{
row.RelativeItem().Column(column =>
{
column.Item().Text($"Invoice #{Model.InvoiceNumber}").FontFamily("simhei").Style(titleStyle);
column.Item().Text(text =>
{
text.Span("Issue date: ").SemiBold().FontFamily("simhei");
text.Span($"{Model.IssueDate:d}").FontFamily("simhei");
});
column.Item().Text(text =>
{
text.Span("Due date: ").FontFamily("simhei").SemiBold();
text.Span($"{Model.DueDate:d}").FontFamily("simhei");
});
});
});
}

Finally, we implement the content:
public void Compose(IDocumentContainer container)
{
container
.Page(page =>
{
page.PageColor(Colors.Red.Lighten1);
page.Size(PageSizes.A4);
page.Margin(10);
page.Header().Height(100).Background(Colors.LightBlue.Lighten1).Element(ComposeHeader);
page.Content().Background(Colors.Grey.Lighten3).Element(ComposeContent);
page.Footer().Height(50).Background(Colors.Grey.Lighten1);
});
}
void ComposeHeader(IContainer container)
{
var titleStyle = TextStyle.Default.FontSize(20).SemiBold().FontColor(Colors.Blue.Medium);
container.Row(row =>
{
row.RelativeItem().Column(column =>
{
column.Item().Text($"Invoice #{Model.InvoiceNumber}").FontFamily("simhei").Style(titleStyle);
column.Item().Text(text =>
{
text.Span("Issue date: ").SemiBold().FontFamily("simhei");
text.Span($"{Model.IssueDate:d}").FontFamily("simhei");
});
column.Item().Text(text =>
{
text.Span("Due date: ").FontFamily("simhei").SemiBold();
text.Span($"{Model.DueDate:d}").FontFamily("simhei");
});
});
});
}
void ComposeContent(IContainer container)
{
container.Table(table =>
{
// step 1: define columns
table.ColumnsDefinition(columns =>
{
columns.ConstantColumn(25);
columns.RelativeColumn(3);
columns.RelativeColumn();
columns.RelativeColumn();
columns.RelativeColumn();
});
// step 2: header
table.Header(header =>
{
header.Cell().Text("#").FontFamily("simhei");
header.Cell().Text("Product").FontFamily("simhei");
header.Cell().AlignRight().Text("Price").FontFamily("simhei");
header.Cell().AlignRight().Text("Quantity").FontFamily("simhei");
header.Cell().AlignRight().Text("Total").FontFamily("simhei");
header.Cell().ColumnSpan(5)
.PaddingVertical(5).BorderBottom(1).BorderColor(Colors.Black);
});
// step 3: data rows
foreach (var item in Model.Items)
{
table.Cell().Element(CellStyle).Text(Model.Items.IndexOf(item) + 1).FontFamily("simhei");
table.Cell().Element(CellStyle).Text(item.Name).FontFamily("simhei");
table.Cell().Element(CellStyle).AlignRight().Text($"{item.Price}$").FontFamily("simhei");
table.Cell().Element(CellStyle).AlignRight().Text(item.Quantity).FontFamily("simhei");
table.Cell().Element(CellStyle).AlignRight().Text($"{item.Price * item.Quantity}$").FontFamily("simhei");
static IContainer CellStyle(IContainer container)
{
return container.BorderBottom(1).BorderColor(Colors.Grey.Lighten2).PaddingVertical(5);
}
}
});
}
After all these preparations, we can generate the PDF document:
var filePath = "invoice.pdf";
var model = InvoiceDocumentDataSource.GetInvoiceDetails();
var document = new InvoiceDocument(model);
document.GeneratePdf(filePath);

4. Summary
Of course, there are many more interesting features. Today I just gave you an overview to get you familiar with this library. I will continue to share more functionalities of this library in future posts. If you are interested in this library, feel free to follow me! WeChat public account: [黑哥聊 dotNet]