Skip to main content

PDF Generation in C#/.NET Using QuestPDF

· 19 min read
Michał Szymanowski
Michał Szymanowski
PDFBolt Co-Founder

Professional PDF document generation with QuestPDF library in C#/.NET

PDF document generation has long been a necessary but often frustrating aspect of .NET development. Traditional approaches frequently require developers to manage absolute positioning, manual pagination, and complex styling calculations. QuestPDF takes a revolutionary approach to this challenge by providing a layout-based system with an intuitive fluent API that feels natural to C# developers. This comprehensive guide demonstrates how QuestPDF transforms PDF generation from a tedious task into a streamlined, maintainable process through its modern design philosophy and powerful layout engine.

What is QuestPDF?

QuestPDF is an open-source, modern .NET library built specifically for programmatic PDF document creation. It provides a comprehensive layout engine powered by a concise C# Fluent API that handles the complexities of document structure automatically.

Key Features and Capabilities

QuestPDF offers a rich set of features that distinguish it from other PDF generation libraries:

  • Container-based layout system that eliminates the need for absolute positioning and coordinates.
  • Automatic pagination with content overflow handling between pages.
  • Dynamic data binding using standard C# patterns (loops, conditionals).
  • Rich text formatting with full control over fonts, styles, and alignment.
  • Responsive tables and grids with flexible column definitions.
  • SVG and image support with multiple sizing options.

Whether you're creating invoices, reports, contracts, or data exports, QuestPDF's intuitive approach dramatically reduces development time while producing professional-quality documents.

This guide will walk you through everything from basic setup to advanced techniques for implementing sophisticated document generation in your C#/.NET applications.

Important Note

QuestPDF is not an HTML to PDF converter. It provides a programming interface for creating and manipulating PDF documents directly in C#.

If you're looking to convert HTML to PDF, you'll need a separate tool or library. See HTML to PDF Conversion Options for more details.

Getting Started with QuestPDF in C#/.NET Projects

Installation

Add QuestPDF to your .NET project using one of the following methods:

Using the .NET CLI:

dotnet add package QuestPDF

Using the Package Manager Console:

Install-Package QuestPDF

You can also add a reference in your .csproj file:

<PackageReference Include="QuestPDF" Version="2025.4.2" />

Setting Up the License

QuestPDF offers a tiered licensing model:

  • Community License: Free for individuals, non-profits, open-source projects, and businesses with annual revenue below $1M USD. Suitable for most use cases.
  • Professional/Enterprise Licenses: Required for larger organizations.

Add this line at startup to set the license:

QuestPDF.Settings.License = LicenseType.Community;
// or LicenseType.Professional or LicenseType.Enterprise
note

For detailed licensing information, visit the QuestPDF License and Pricing page.

Our First PDF Document

Let's create a simple PDF with some text:

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

// Set license type at startup
QuestPDF.Settings.License = LicenseType.Community;

// Create and generate a PDF document
Document.Create(container =>
{
container.Page(page =>
{
// Configure page settings
page.Size(PageSizes.A4);
page.Margin(2, Unit.Centimetre);
page.PageColor(Colors.White);
page.DefaultTextStyle(x => x.FontSize(12));

// Add a header
page.Header()
.Text("Our First QuestPDF Document")
.SemiBold()
.FontSize(34)
.FontColor(Colors.Pink.Medium);

// Add content
page.Content()
.PaddingVertical(1, Unit.Centimetre)
.Column(column =>
{
column.Item().Text("Grab a coffee ☕ and let's make some PDFs!")
.FontSize(20)
.SemiBold();
});
});
})
.GeneratePdf("first-doc.pdf");

Mission accomplished – take a look at the result:

Simple PDF document created using QuestPDF in C#/.NET

Working with Text in PDFs Using QuestPDF

QuestPDF provides rich text formatting capabilities that allow you to create professional-looking documents with styled text.

Here's an example showcasing various text formatting options:

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

// Set license type at startup
QuestPDF.Settings.License = LicenseType.Community;

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

page.Header()
.Text("Text Formatting Options in QuestPDF")
.SemiBold()
.FontSize(24);

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

column.Item()
.Text("Standard text with purple color")
.FontColor(Colors.Purple.Medium);

column.Item()
.Text("Bold text with orange color")
.Bold()
.FontColor(Colors.Orange.Medium);

column.Item()
.Text("Italic text with right alignment")
.Italic()
.AlignRight();

column.Item()
.Text("Underlined text with letter spacing")
.Underline()
.LetterSpacing((float)0.5);

column.Item()
.Text("Strikethrough text with large font")
.Strikethrough()
.FontSize(18);

column.Item()
.Text("Centered, bold and italic text")
.Bold()
.Italic()
.AlignCenter();


column.Item()
.Text(text =>
{
text.Span("Mixed ").FontSize(12);
text.Span("formatting ").Bold().FontColor(Colors.Blue.Medium);
text.Span("in ").Italic();
text.Span("one ").Underline();
text.Span("line").FontSize(18).FontColor(Colors.Red.Medium);
});

column.Item()
.Text(
"Justified text with longer content. This text demonstrates how QuestPDF handles justified alignment with multiple lines of content in a single text block.")
.Justify();
});
});
})
.GeneratePdf("text-formatting.pdf");

This example demonstrates a variety of text formatting options available in QuestPDF:

OptionDescriptionExample
Font ColorsSets the color of text..FontColor()
Font WeightsControls the thickness of the text..Bold()
.SemiBold()
Font StylesCreates slanted text (italic)..Italic()
Text DecorationsAdds lines to text, such as underlining or strikethrough..Underline()
.Strikethrough()
Text AlignmentControls the positioning of the text..AlignLeft()
.AlignRight()
.AlignCenter()
.Justify()
Font SizingChanges the size of the text in points..FontSize()
Letter SpacingAdjusts the space between characters..LetterSpacing()
Mixed FormattingCombines multiple styles in one line using Span()..Text(text => { text.Span()... })

Here is the output:

Working with Text Formatting using QuestPDF in C#/.NET

Working with Images in PDFs Using QuestPDF

QuestPDF makes it easy to embed images in your documents with flexible positioning and sizing options.

Here's a simple example demonstrating how to work with images:

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

// Set license type at startup
QuestPDF.Settings.License = LicenseType.Community;

Document.Create(container =>
{
container.Page(page =>
{
page.Size(PageSizes.A4);
page.Margin(1, Unit.Centimetre);
page.PageColor(Colors.White);
page.DefaultTextStyle(x => x.FontSize(12));

page.Content()
.PaddingVertical(1, Unit.Centimetre)
.Column(column =>
{
// Standard image - will scale to fit width
column.Item()
.Padding(10)
.Column(imageSection =>
{
imageSection.Item().Text("Default Image (FitWidth)").Bold();
imageSection.Item().PaddingBottom(10);
imageSection.Item().Image("image.jpg");
});

column.Spacing(10);

// Image with specific width
column.Item()
.Padding(10)
.Column(imageSection =>
{
imageSection.Item().Text("Image with Specific Width").Bold();
imageSection.Item().PaddingBottom(10);
imageSection.Item().Width(150).Image("image.jpg");
});

column.Spacing(10);

// Row of images
column.Item()
.Padding(10)
.Column(imageSection =>
{
imageSection.Item().Text("Multiple Images in a Row").Bold();
imageSection.Item().PaddingBottom(10);

imageSection.Item().Row(row =>
{
row.Spacing(10);
row.RelativeItem().Image("image.jpg");
row.RelativeItem().Image("image.jpg");
row.RelativeItem().Image("image.jpg");
});
});
});
});
})
.GeneratePdf("image-examples.pdf");

PDF preview:

PDF with images using QuestPDF in C#/.NET


info

QuestPDF supports the most common image formats including JPEG, PNG, BMP, and WEBP. The library automatically handles image scaling and positioning, making it easy to integrate visual elements in your documents.

Image Sizing

In QuestPDF, specify only Width() or Height() for images, not both. The library preserves aspect ratio by default and handles the pixel-to-point conversion automatically.

container
.Width(1, Unit.Inch)
.Image("image.jpg")

Working with Tables in PDFs Using QuestPDF

Tables in QuestPDF offer great flexibility for displaying structured data.

Here's an example that demonstrates column definitions, cell spanning, and basic styling:

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

// Set license type at startup
QuestPDF.Settings.License = LicenseType.Community;

Document.Create(container =>
{
container.Page(page =>
{
page.Size(PageSizes.A4);
page.Margin(1, Unit.Centimetre);
page.PageColor(Colors.White);
page.DefaultTextStyle(x => x.FontSize(12));

page.Header()
.Text("Simple Table")
.SemiBold()
.FontSize(20);

page.Content()
.PaddingVertical(1, Unit.Centimetre)
.Table(table =>
{
table.ColumnsDefinition(columns =>
{
columns.ConstantColumn(180); // Fixed width column
columns.RelativeColumn(3); // Takes 3 parts of remaining width
columns.RelativeColumn(1); // Takes 1 part of remaining width
});

// Header row spanning all columns
table.Cell().ColumnSpan(3)
.Background(Colors.Grey.Lighten2).Element(CellStyle)
.Text("Custom Column Widths")
.FontSize(14)
.Bold();

// Column description row
table.Cell().Element(CellStyle).Text("Fixed Width");
table.Cell().Element(CellStyle).Text("Relative Width (75%)");
table.Cell().Element(CellStyle).Text("Relative Width (25%)");

// Example width values
table.Cell().Element(CellStyle).Text("Banana 🍌");
table.Cell().Element(CellStyle).Text("Coffee ☕");
table.Cell().Element(CellStyle).Text("Deadline 😱");

// Helper method for consistent cell styling
static IContainer CellStyle(IContainer container)
=> container.Border(1).BorderColor(Colors.Grey.Medium).Padding(10);
});
});
})
.GeneratePdf("table-example.pdf");

Preview of the Generated PDF

PDF with table layout using QuestPDF in C#/.NET

Working with Headers and Footers in PDFs Using QuestPDF

QuestPDF makes it easy to create consistent headers and footers across multiple pages.

Here's an example demonstrating a document with headers and footers:

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

// Set license type at startup
QuestPDF.Settings.License = LicenseType.Community;

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

// Add a header
page.Header().Element(ComposeHeader);

// Add content
page.Content().Element(ComposeContent);

// Add a footer
page.Footer().Element(ComposeFooter);
});
})
.GeneratePdf("header-footer-example.pdf");

// Simple header composition
void ComposeHeader(IContainer container)
{
container.Border(1).BorderColor(Colors.Grey.Medium).Padding(10)
.Row(row =>
{
row.RelativeItem().Column(column =>
{
column.Item().Text("Document Title")
.FontSize(16)
.Bold();

column.Item().Text($"Generated: {DateTime.Now:d}")
.FontSize(10)
.FontColor(Colors.Grey.Medium);
});
});
}

// Main content composition
void ComposeContent(IContainer container)
{
container.PaddingVertical(10).Column(column =>
{
// Add some content for demonstration
column.Item().Text("First Page Content").FontSize(14).Bold();
column.Item().PaddingTop(5).Text(Placeholders.LoremIpsum());
column.Item().PaddingTop(10).Text(Placeholders.LoremIpsum());

// Add a line break to demonstrate multi-page content
column.Item().PageBreak();

// Continue content on next page
column.Item().Text("Second Page Content").FontSize(14).Bold();
column.Item().PaddingTop(5).Text(Placeholders.LoremIpsum());
column.Item().PaddingTop(10).Text(Placeholders.LoremIpsum());
});
}

// Simple footer composition
void ComposeFooter(IContainer container)
{
container.Border(1).BorderColor(Colors.Grey.Medium)
.PaddingVertical(5).PaddingHorizontal(10)
.Row(row =>
{
row.RelativeItem().AlignLeft().Text("Company Name")
.FontSize(10);

row.RelativeItem().AlignRight().Text(text =>
{
text.Span("Page ");
text.CurrentPageNumber();
text.Span(" of ");
text.TotalPages();
});
});
}

The header and footer appear on every page automatically, providing a consistent look and feel throughout the document.

Output Preview

Document with headers and footers generated using QuestPDF in C#/.NET
Document with headers and footers generated using QuestPDF in C#/.NET

Adding Barcodes and QR Codes to PDFs with QuestPDF

QuestPDF supports generating various types of barcodes through integration with the ZXing.Net library. This allows you to embed both linear barcodes and QR codes directly in your PDF documents.

Important Note

Before using any of these examples, you must install the ZXing.Net package:

dotnet add package ZXing.Net

Here's a simple example showing how to create both linear barcodes and QR codes in your documents:

using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
using ZXing;
using ZXing.OneD;
using ZXing.QrCode;
using ZXing.Rendering;

// Set license type at startup
QuestPDF.Settings.License = LicenseType.Community;

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

page.Content()
.PaddingVertical(1, Unit.Centimetre)
.Column(column =>
{
// 1. Linear Barcode Example (Code 128)
column.Item()
.Background(Colors.Grey.Lighten4)
.Padding(15)
.Column(barcodeSection =>
{
barcodeSection.Item().Text("Code 128 Barcode").Bold();
barcodeSection.Item().PaddingBottom(10);

// Product code to encode
var productCode = "PRD12345";

barcodeSection.Item()
.Height(80)
.AlignCenter()
.Background(Colors.White)
.Padding(10)
.Svg(size =>
{
var writer = new Code128Writer();
var matrix = writer.encode(productCode, BarcodeFormat.CODE_128,
(int)size.Width, (int)size.Height);
var renderer = new SvgRenderer();
return renderer.Render(matrix, BarcodeFormat.CODE_128, productCode).Content;
});

barcodeSection.Item().PaddingBottom(10);
barcodeSection.Item()
.AlignCenter()
.Text(productCode);
});

column.Spacing(20);

// 2. QR Code Example
column.Item()
.Background(Colors.Grey.Lighten4)
.Padding(15)
.Column(qrSection =>
{
qrSection.Item().Text("QR Code").Bold();
qrSection.Item().Height(10);

// URL to encode
var url = "https://pdfbolt.com";

qrSection.Item()
.Height(150)
.AlignCenter()
.Background(Colors.White)
.Padding(10)
.Svg(size =>
{
var writer = new QRCodeWriter();
var matrix = writer.encode(url, BarcodeFormat.QR_CODE,
(int)size.Width, (int)size.Height);
var renderer = new SvgRenderer();
return renderer.Render(matrix, BarcodeFormat.QR_CODE, null).Content;
});

qrSection.Item().PaddingBottom(10);
qrSection.Item()
.AlignCenter()
.Text(url)
.FontColor(Colors.Blue.Medium);
});
});
});
})
.GeneratePdf("barcode-qrcode-examples.pdf");

These examples demonstrate:

  1. Linear Barcode (Code 128): Suitable for product IDs, tracking numbers, and other alphanumeric data.
  2. QR Code: Ideal for encoding URLs, contact information, or larger amounts of text.

Both examples use SVG rendering for high-quality output at any resolution, ensuring that your barcodes remain scannable in the final PDF document.

Customization
  • You can adjust the height and padding of the barcodes to fit your needs.
  • QR codes can encode much more data than shown in this example (URLs, contact info, etc.).

See the Result:

PDF with Barcode and QR Code using QuestPDF in C#/.NET

Step-by-Step: Creating a Professional Invoice PDF with QR Code in C#

Let's create a complete example of a professional-looking invoice with QuestPDF, including a company logo, structured layout, and a QR code for payment information.

Step 1: Project Setup

First, let's create the project structure:

InvoiceGenerator/

├── Program.cs # Main application entry point
├── InvoiceDocument.cs # Invoice document definition
├── InvoiceModel.cs # Data model for the invoice
├── Assets/
│ └── logo.png # Company logo
└── Helpers/
└── InvoiceStyle.cs # Styling helpers for the invoice

Make Sure your project has the necessary dependencies installed:

dotnet list package

You should see output that includes entries like:

  • QuestPDF
  • ZXing.Net

If they’re not listed, install them using:

dotnet add package QuestPDF
dotnet add package ZXing.Net

Step 2: Create the Invoice Model

Now let's create our data model that will hold all the invoice information.

Create a file named InvoiceModel.cs with the following code:

InvoiceModel.cs
namespace InvoiceGenerator
{
public class InvoiceModel
{
// Invoice metadata
public string? InvoiceNumber { get; set; }
public DateTime IssueDate { get; set; } = DateTime.Now;
public DateTime DueDate { get; set; } = DateTime.Now.AddDays(14);

// Company information
public CompanyInfo? Seller { get; set; }
public CompanyInfo? Customer { get; set; }

// Invoice content
public List<InvoiceItem> Items { get; set; } = new();
public string? Comments { get; set; }

// Payment details for QR code
public string? PaymentAccountNumber { get; set; }
public string? PaymentReference { get; set; }

// Calculated properties
public decimal Subtotal => Items.Sum(x => x.TotalPrice);
public decimal TaxRate { get; set; } = 0.23m; // Default 23% VAT
public decimal TaxAmount => Subtotal * TaxRate;
public decimal GrandTotal => Subtotal + TaxAmount;
}

public class CompanyInfo
{
public string? Name { get; set; }
public string? Street { get; set; }
public string? City { get; set; }
public string? State { get; set; }
public string? Zip { get; set; }
public string? Email { get; set; }
public string? Phone { get; set; }
}

public class InvoiceItem
{
public string? Name { get; set; }
public decimal UnitPrice { get; set; }
public int Quantity { get; set; }
public decimal TotalPrice => UnitPrice * Quantity;
}
}

Step 3: Create the Invoice Styling Helper

Next, we'll create a helper class to maintain consistent styling throughout our invoice document. This centralizes our colors and text styles in one place.

Create a file named Helpers/InvoiceStyle.cs with common styling methods:

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

namespace InvoiceGenerator.Helpers
{
public static class InvoiceStyle
{
// Colors
public static Color PrimaryColor => Color.FromHex("#916E56");
public static Color TextColor => Colors.Black;

// Text Styles
public static TextStyle Title => TextStyle.Default.FontSize(24).SemiBold().FontColor(PrimaryColor);
public static TextStyle Subtitle => TextStyle.Default.FontSize(13).SemiBold().FontColor(TextColor);
public static TextStyle Normal => TextStyle.Default.FontSize(11).FontColor(TextColor).LineHeight((float?)1.4);
}
}

Step 4: Create the Invoice Document Class

Now we'll create the core class that implements the QuestPDF IDocument interface. This class will define the overall structure of our invoice and handle the rendering of all components.

Create a file named InvoiceDocument.cs:

InvoiceDocument.cs
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
using ZXing;
using ZXing.QrCode;
using ZXing.Rendering;
using InvoiceGenerator.Helpers;
using System.Globalization;

namespace InvoiceGenerator
{
public class InvoiceDocument : IDocument
{
public InvoiceModel Model { get; }

public InvoiceDocument(InvoiceModel model) => Model = model;

public DocumentMetadata GetMetadata() => DocumentMetadata.Default;

// Main document composition
public void Compose(IDocumentContainer container)
{
// Set US culture for $ currency formatting
var culture = new CultureInfo("en-US");
Thread.CurrentThread.CurrentCulture = culture;

container
.Page(page =>
{
page.Size(PageSizes.A4);
page.Margin(50);
page.DefaultTextStyle(x => x.FontSize(10));

page.Header().Element(ComposeHeader);
page.Content().Element(ComposeContent);
page.Footer().Element(ComposeFooter);
});
}

// Simple footer composition
void ComposeFooter(IContainer container)
{
container.Border(1).BorderColor(Colors.Grey.Lighten2)
.PaddingVertical(5).PaddingHorizontal(10)
.Row(row =>
{
row.RelativeItem().AlignLeft().Text(Model.Seller?.Name)
.FontSize(10);

row.RelativeItem().AlignRight().Text(text =>
{
text.Span("Page ");
text.CurrentPageNumber();
text.Span(" of ");
text.TotalPages();
});
});
}

// Document header with logo, invoice details and QR code
private void ComposeHeader(IContainer container)
{
container.Row(row =>
{
// Left column: Logo and basic invoice info
row.RelativeItem().Column(column =>
{
column.Item().Height(80).Image("Assets/logo.png");
column.Item().PaddingBottom(10);
column.Item().Text($"Invoice Number: {Model.InvoiceNumber}").Style(InvoiceStyle.Normal);
column.Item().Text($"Issue Date: {Model.IssueDate:d}").Style(InvoiceStyle.Normal);
column.Item().Text($"Due Date: {Model.DueDate:d}").Style(InvoiceStyle.Normal);
});

// Right column: Title and QR code
row.RelativeItem().Column(column =>
{
column.Item().AlignRight().Text("INVOICE").Style(InvoiceStyle.Title);
column.Item().PaddingBottom(10);

// Payment QR code for scanning
column.Item().AlignRight().Width(90).AspectRatio(1).Svg(GenerateQrCode);
});
});
}

// Generate QR code with payment information
private string GenerateQrCode(Size size)
{
string qrData =
$"Recipient:{Model.Seller?.Name}|AccNo:{Model.PaymentAccountNumber}|Ref:{Model.PaymentReference}|Amount:{Model.GrandTotal:F2}";
var matrix = new QRCodeWriter().encode(qrData, BarcodeFormat.QR_CODE, (int)size.Width, (int)size.Height);
return new SvgRenderer().Render(matrix, BarcodeFormat.QR_CODE, null).Content;
}

// Main content area
private void ComposeContent(IContainer container)
{
container.Column(column =>
{
column.Item().PaddingTop(20).Element(ComposeBillingAndShippingInfo);
column.Item().PaddingTop(20).Element(ComposeInvoiceItems);
column.Item().PaddingTop(10).AlignRight().Width(250).Element(ComposeTotals);

// Only show comments if present
if (!string.IsNullOrEmpty(Model.Comments))
column.Item().PaddingTop(20).Element(ComposeComments);
});
}

// Billing and shipping information section
private void ComposeBillingAndShippingInfo(IContainer container)
{
container.Row(row =>
{
// Bill To section
ComposeAddressSection(row.RelativeItem(), "BILL TO", Model.Customer);

row.ConstantItem(50); // Spacing between sections

// Ship To section
ComposeAddressSection(row.RelativeItem(), "SHIP TO", Model.Seller);
});
}

// Reusable address section formatter
private void ComposeAddressSection(IContainer container, string title, CompanyInfo? company)
{
container.Column(column =>
{
column.Item().Text(title).Style(InvoiceStyle.Subtitle).FontColor(InvoiceStyle.PrimaryColor);
column.Item().PaddingBottom(5);
column.Item().LineHorizontal(1).LineColor(Colors.Grey.Lighten1);
column.Item().PaddingBottom(5);

if (company != null)
{
column.Item().Text(company.Name).Bold().Style(InvoiceStyle.Normal);
column.Item().Text(company.Street).Style(InvoiceStyle.Normal);
column.Item().Text($"{company.City}, {company.State} {company.Zip}").Style(InvoiceStyle.Normal);
column.Item().Text($"Phone: {company.Phone}").Style(InvoiceStyle.Normal);
column.Item().Text($"Email: {company.Email}").Style(InvoiceStyle.Normal);
}
});
}

// Invoice items table
private void ComposeInvoiceItems(IContainer container)
{
container.Table(table =>
{
// Table columns definition
table.ColumnsDefinition(columns =>
{
columns.RelativeColumn(3); // Description
columns.ConstantColumn(100); // Unit Price
columns.ConstantColumn(80); // Quantity
columns.ConstantColumn(100); // Total
});

// Table header
AddTableHeader(table);

// Table rows with alternating colors
bool isAlternateRow = false;
foreach (var item in Model.Items)
{
AddTableRow(table, item, isAlternateRow);
isAlternateRow = !isAlternateRow;
}
});
}

// Table header helper
private void AddTableHeader(TableDescriptor table)
{
table.Header(header =>
{
header.Cell().Background(Colors.Grey.Lighten3)
.Padding(5).Text("Description")
.Style(InvoiceStyle.Subtitle);

header.Cell().Background(Colors.Grey.Lighten3)
.Padding(5).AlignRight().Text("Unit Price")
.Style(InvoiceStyle.Subtitle);

header.Cell().Background(Colors.Grey.Lighten3)
.Padding(5).AlignRight().Text("Quantity")
.Style(InvoiceStyle.Subtitle);

header.Cell().Background(Colors.Grey.Lighten3)
.Padding(5).AlignRight().Text("Amount")
.Style(InvoiceStyle.Subtitle);
});
}

// Table row helper
private void AddTableRow(TableDescriptor table, InvoiceItem item, bool isAlternateRow)
{
var bgColor = isAlternateRow ? Colors.White : Colors.Grey.Lighten5;

// Description cell
table.Cell().Background(bgColor)
.Border(1).BorderColor(Colors.Grey.Lighten2)
.Padding(5).AlignRight().AlignMiddle()
.Text($"{item.Name}");

// Unit price cell
table.Cell().Background(bgColor)
.Border(1).BorderColor(Colors.Grey.Lighten2)
.Padding(5).AlignRight().AlignMiddle()
.Text($"{item.UnitPrice:C}");

// Quantity cell
table.Cell().Background(bgColor)
.Border(1).BorderColor(Colors.Grey.Lighten2)
.Padding(5).AlignRight().AlignMiddle()
.Text($"{item.Quantity}");

// Total cell
table.Cell().Background(bgColor)
.Border(1).BorderColor(Colors.Grey.Lighten2)
.Padding(5).AlignRight().AlignMiddle()
.Text($"{item.TotalPrice:C}");
}

// Invoice totals section
private void ComposeTotals(IContainer container)
{
container.Table(table =>
{
table.ColumnsDefinition(columns =>
{
columns.RelativeColumn();
columns.ConstantColumn(180);
});

// Subtotal row
table.Cell().PaddingBottom(8).AlignRight().Text("Subtotal:").Style(InvoiceStyle.Normal);
table.Cell().Padding(5).AlignRight().Text($"{Model.Subtotal:C}");

// Tax row
table.Cell().PaddingBottom(8).AlignRight()
.Text($"Tax ({Model.TaxRate:P0}):").Style(InvoiceStyle.Normal);
table.Cell().Padding(5).AlignRight().Text($"{Model.TaxAmount:C}");

// Total row
table.Cell().BorderLeft(3).BorderColor(InvoiceStyle.PrimaryColor)
.Background(Colors.Grey.Lighten4).Padding(8).AlignRight()
.Text("TOTAL:").Style(InvoiceStyle.Subtitle);
table.Cell().Background(Colors.Grey.Lighten4).Padding(8)
.AlignRight().Text($"{Model.GrandTotal:C}").Bold().FontSize(12);
});
}

// Optional comments section
private void ComposeComments(IContainer container)
{
container.Background(Colors.Grey.Lighten4)
.BorderLeft(4).BorderColor(InvoiceStyle.PrimaryColor).Padding(12)
.Column(column =>
{
column.Item().Text("Note").Style(InvoiceStyle.Subtitle);
column.Spacing(10);
column.Item().Text(Model.Comments).Style(InvoiceStyle.Normal);
});
}
}
}

Step 5: Create the Program Entry Point

Finally, let's create the main program that ties everything together. This file will set up the QuestPDF license, create sample invoice data, and generate the final PDF document.

Create a file named Program.cs:

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

namespace InvoiceGenerator
{
class Program
{
static void Main(string[] args)
{
// Set license
QuestPDF.Settings.License = LicenseType.Community;

// Create and generate the invoice
var document = new InvoiceDocument(GetSampleInvoice());
document.GeneratePdf("invoice.pdf");

Console.WriteLine("Invoice generated successfully: invoice.pdf");
}

// Create a sample invoice for testing
static InvoiceModel GetSampleInvoice() => new()
{
InvoiceNumber = $"INV-{DateTimeOffset.Now.ToUnixTimeSeconds()}",
IssueDate = DateTime.Now,
DueDate = DateTime.Now.AddDays(30),
PaymentAccountNumber = "9876543210",
PaymentReference = "INV-PAYMENT",

Seller = new CompanyInfo
{
Name = "Coffee Point",
Street = "404 Bean Not Found",
City = "Latteville",
State = "NY",
Zip = "12345",
Email = "[email protected]",
Phone = "(555) 123-4567"
},

Customer = new CompanyInfo
{
Name = "Latte Lovers",
Street = "101 Cappuccino St.",
City = "Milktown",
State = "NY",
Zip = "10001",
Email = "[email protected]",
Phone = "(555) 987-6543"
},

Items = new List<InvoiceItem>
{
new()
{
Name = "Custom Coffee Blend",
UnitPrice = 15.00m,
Quantity = 20
},
new()
{
Name = "Monthly Bean Subscription",
UnitPrice = 59.00m,
Quantity = 3
},
new()
{
Name = "Espresso Machine Maintenance",
UnitPrice = 115.00m,
Quantity = 1
},
new()
{
Name = "Espresso Support Package",
UnitPrice = 195.00m,
Quantity = 1
}
},

Comments =
"Please use the QR code for quick payment or include the invoice number with your payment. Thank you for your business!",
};
}
}
  1. Create an Assets folder in your project directory.
  2. Add your company logo as logo.png in this folder.

Step 7: Run the Application

dotnet run

This will generate an invoice.pdf file in your project directory.

The generated invoice will have:

  1. Header Section:

    • Company logo.
    • Invoice number and dates.
    • QR code containing payment information.
  2. Main Content:

    • Billing and shipping information sections.
    • Detailed table of invoice items.
    • Subtotal, tax, and grand total calculations.
    • Optional comments section.
  3. Footer:

    • Company name.
    • Page numbering

Coffee in hand ☕ – time to check out our Invoice PDF preview: Invoice PDF generated using QuestPDF in C#/.NET

Best Practices for Efficient PDF Generation with QuestPDF in .NET

To ensure your PDF documents are optimized for performance, maintainability, and scalability, follow these best practices when working with QuestPDF in your .NET applications:

PracticeDescription
Modular DesignBreak down complex documents into reusable components and methods.
Memory ManagementUse streams for large documents to reduce memory usage.
Error HandlingWrap PDF generation in try-catch blocks with meaningful error messages.
Image OptimizationOptimize image size and resolution before adding to documents.
Font ManagementUse standard fonts or properly embed custom fonts.
Code OrganizationSeparate document structure, data, and styling concerns.
Hot-ReloadUse ShowInPreviewer() during development for real-time preview.

HTML to PDF Conversion Options

While QuestPDF is designed for programmatic PDF creation through C# code rather than HTML conversion, many developers need to convert existing HTML content to PDF.

Here's a comparison of your options:

ApproachToolsBest ForConsLearn More
HTML ParsingAngleSharpComplex HTML documents requiring DOM manipulation.Requires manual mapping to QuestPDF components.AngleSharp on GitHub
HTML ExtensionHTMLToQPDFDirect HTML insertion into QuestPDF documents.Limited HTML/CSS feature support.HTMLToQPDF on GitHub
HTML to PDF APIsPDFBoltProduction-grade conversions with complex layouts.External service.HTML to PDF API Guide
Browser AutomationPuppeteerSharp
Playwright
Pixel-perfect rendering of modern web apps.Higher resource usage, more complex setupGenerate PDF Using Playwright in C#/.NET
HTML to PDF in C#/.NET Using PuppeteerSharp

Conclusion

QuestPDF stands out as a powerful and intuitive library for PDF generation in .NET applications. Its layout-based approach and fluent API make it significantly easier to create complex, visually appealing documents compared to traditional libraries that rely on absolute positioning. The modern API feels natural for C# developers, while automatic pagination and content flow management simplify even the most complex layouts.

With rich formatting capabilities for text, tables, and images, seamless integration with modern .NET frameworks, and strong community support, QuestPDF offers everything needed for professional document generation.

For HTML to PDF conversion, consider using a dedicated solution like PDFBolt’s HTML to PDF API. It provides high-fidelity rendering that preserves advanced layouts and styles for polished, production-ready PDFs.

Thanks for reading – you've earned a coffee break (or three)! ☕☕☕