NashTech Blog

Table of Contents

A practical integration guide with code, patterns, and production tips

Why QuestPDF?

QuestPDF is a modern, fluent C# library for programmatic PDF generation. It’s fast, strongly-typed, testable, and designed for server-side scenarios (APIs, background jobs, Lambdas/Functions). Compared to HTML-to-PDF tools, you get deterministic layout without a headless browser, and compared to Office automation, you avoid COM headaches and licensing traps.

Install & Setup

NuGet

dotnet add package QuestPDF

Namespaces

using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
using QuestPDF.Drawing;

Mental Model (How it renders)

QuestPDF builds a layout tree (like a constrained flexbox). You define pages and compose containers using fluent builders:

  • PageHeader, Content, Footer
  • Inside content: Column, Row, Stack, Table, Grid, Image, Text
  • Styles cascade via TextStyle.Default or explicitly per element
  • ShowOnce(), PageNumber(), Section() and GeneratePdf/GeneratePdfAsync control output behavior

Fonts, Unicode (e.g. Vietnamese), and Branding

If you render Vietnamese or other non-ASCII glyphs, register a font that supports them:

FontManager.RegisterFont(File.OpenRead("Assets/Fonts/Roboto-Regular.ttf"));
FontManager.RegisterFont(File.OpenRead("Assets/Fonts/Roboto-Bold.ttf"));

TextStyle.Default
.Fallback(x => x.FontFamily("Roboto")) // or your chosen font
.FontSize(10);

Images & Logos

byte[] logo = await File.ReadAllBytesAsync("Assets/Logo.png");
page.Header().Row(r =>
{
r.RelativeItem().Text("Your Title").FontSize(18).Bold();
r.ConstantItem(64).Height(64).Image(logo);
});

Pagination, Long Tables, and Performance

QuestPDF automatically paginates long content. For large tables:

  • Keep rows lightweight (avoid heavy per-row images).
  • Prefer value types / primitives in your DTOs.
  • Consider streaming data into the model and pre-chunking long datasets for very large outputs (e.g., 50k+ rows).

For multi-document batch jobs, reuse:

  • FontManager registration at app start
  • Static styles and shared byte[] assets

Use GeneratePdfAsync if you’re in an async pipeline, but avoid blocking on sync.

A Complete Example: Invoice Generation

Install

dotnet new console -n QuestPdfMinimal
cd QuestPdfMinimal
dotnet add package QuestPDF

Program.cs

using QuestPDF.Fluent;
using QuestPDF.Infrastructure;
using QuestPDF.Helpers;
using System.Globalization;

record LineItem(string Name, int Qty, decimal UnitPrice, string Notes);

class InvoiceDocument(LineItem[] items) : IDocument
{
public DocumentMetadata GetMetadata() => new() { Title = "Sample Invoice (QuestPDF)", Author = "Acme Corp" };

public void Compose(IDocumentContainer container)
{
container.Page(page =>
{
page.Margin(36);
page.Size(PageSizes.A4);
page.DefaultTextStyle(TextStyle.Default.FontSize(10).Fallback(x => x.FontFamily("Times")));

page.Header().Row(row =>
{
row.RelativeItem().Column(col =>
{
col.Item().Text("Sample Invoice (QuestPDF)").FontSize(18).SemiBold();
col.Item().Text("Acme Corp").FontColor(Colors.Grey.Darken1);
col.Item().Text($"Generated at (UTC): {DateTime.UtcNow:yyyy-MM-dd HH:mm:ss}");
col.Item().Text("Customer: Pennies Charity");
});

row.ConstantItem(200).AlignRight().Column(col =>
{
col.Item().Text($"Invoice # INV-2025-001").SemiBold();
col.Item().Text($"Due Date: {DateTime.UtcNow.AddDays(7):yyyy-MM-dd}");
col.Item().Text($"Currency: GBP");
});
});

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

// Summary
col.Item().Border(1).BorderColor(Colors.Grey.Lighten2).Padding(10).Column(box =>
{
var subtotal = items.Sum(i => i.Qty * i.UnitPrice);
var vat = subtotal * 0.20m;
var total = subtotal + vat;

box.Item().Text("Summary").SemiBold();
box.Item().Text($"• Items: {items.Length}");
box.Item().Text($"• Subtotal: {subtotal.ToString("C", CultureInfo.GetCultureInfo("en-GB"))}");
box.Item().Text($"• VAT (20%): {vat.ToString("C", CultureInfo.GetCultureInfo("en-GB"))}");
box.Item().Text($"• Total: {total.ToString("C", CultureInfo.GetCultureInfo("en-GB"))}").SemiBold();
});

// Table
col.Item().Table(table =>
{
table.ColumnsDefinition(cols =>
{
cols.RelativeColumn(3); // Item
cols.RelativeColumn(1); // Qty
cols.RelativeColumn(2); // Unit Price
cols.RelativeColumn(2); // Line Total
cols.RelativeColumn(3); // Notes
});

table.Header(h =>
{
h.Cell().Background(Colors.Grey.Lighten3).Padding(6).Text("Item").SemiBold();
h.Cell().Background(Colors.Grey.Lighten3).Padding(6).Text("Qty").SemiBold();
h.Cell().Background(Colors.Grey.Lighten3).Padding(6).Text("Unit Price").SemiBold();
h.Cell().Background(Colors.Grey.Lighten3).Padding(6).Text("Line Total").SemiBold();
h.Cell().Background(Colors.Grey.Lighten3).Padding(6).Text("Notes").SemiBold();
});

foreach (var it in items)
{
var lineTotal = it.Qty * it.UnitPrice;
table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2).Padding(6).Text(it.Name);
table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2).Padding(6).Text(it.Qty.ToString());
table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2).Padding(6)
.Text(it.UnitPrice.ToString("C", CultureInfo.GetCultureInfo("en-GB")));
table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2).Padding(6)
.Text(lineTotal.ToString("C", CultureInfo.GetCultureInfo("en-GB")));
table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2).Padding(6).Text(it.Notes);
}
});
});

page.Footer().Row(row =>
{
row.RelativeItem().Text("© 2025 Acme Corp — Confidential").FontColor(Colors.Grey.Darken1);
row.ConstantItem(160).AlignRight().Text(x =>
{
x.Span("Page "); x.CurrentPageNumber(); x.Span(" / "); x.TotalPages();
});
});
});
}
}

class Program
{
static void Main()
{
var items = new[]
{
new LineItem("Donation Processing", 1, 3.50m, "Validated"),
new LineItem("Compliance Review", 1, 2.00m, "No issues"),
new LineItem("Settlement Export", 1, 1.00m, "CSV + PDF"),
};

var doc = new InvoiceDocument(items);
doc.GeneratePdf("SampleInvoice.pdf");
Console.WriteLine("Generated SampleInvoice.pdf");
}
}

The Result

References (official & community)

  • Official site & docs: https://www.questpdf.com/
  • GitHub repository: https://github.com/QuestPDF/QuestPDF
  • Samples gallery (official): https://www.questpdf.com/samples.html
  • Release notes / API changes: GitHub Releases tab
  • Discord / Discussions: linked from the GitHub README

Tip: Always check the latest docs for API changes (especially around IDocument, page sizing, and new components).

Picture of Thinh Tran Hoang Quoc

Thinh Tran Hoang Quoc

Leave a Comment

Suggested Article

Discover more from NashTech Blog

Subscribe now to keep reading and get access to the full archive.

Continue reading