PDF Generation in C#/.NET Using QuestPDF
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.
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
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:
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:
Option | Description | Example |
---|---|---|
Font Colors | Sets the color of text. | .FontColor() |
Font Weights | Controls the thickness of the text. | .Bold() .SemiBold() |
Font Styles | Creates slanted text (italic). | .Italic() |
Text Decorations | Adds lines to text, such as underlining or strikethrough. | .Underline() .Strikethrough() |
Text Alignment | Controls the positioning of the text. | .AlignLeft() .AlignRight() .AlignCenter() .Justify() |
Font Sizing | Changes the size of the text in points. | .FontSize() |
Letter Spacing | Adjusts the space between characters. | .LetterSpacing() |
Mixed Formatting | Combines multiple styles in one line using Span() . | .Text(text => { text.Span()... }) |
Here is the output:
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:
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.
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
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


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.
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:
- Linear Barcode (Code 128): Suitable for product IDs, tracking numbers, and other alphanumeric data.
- 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.
- 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:
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!",
};
}
}
Step 6: Add a Logo
- Create an
Assets
folder in your project directory. - 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:
-
Header Section:
- Company logo.
- Invoice number and dates.
- QR code containing payment information.
-
Main Content:
- Billing and shipping information sections.
- Detailed table of invoice items.
- Subtotal, tax, and grand total calculations.
- Optional comments section.
-
Footer:
- Company name.
- Page numbering
Coffee in hand ☕ – time to check out our Invoice PDF preview:
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:
Practice | Description |
---|---|
Modular Design | Break down complex documents into reusable components and methods. |
Memory Management | Use streams for large documents to reduce memory usage. |
Error Handling | Wrap PDF generation in try-catch blocks with meaningful error messages. |
Image Optimization | Optimize image size and resolution before adding to documents. |
Font Management | Use standard fonts or properly embed custom fonts. |
Code Organization | Separate document structure, data, and styling concerns. |
Hot-Reload | Use 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:
Approach | Tools | Best For | Cons | Learn More |
---|---|---|---|---|
HTML Parsing | AngleSharp | Complex HTML documents requiring DOM manipulation. | Requires manual mapping to QuestPDF components. | • AngleSharp on GitHub |
HTML Extension | HTMLToQPDF | Direct HTML insertion into QuestPDF documents. | Limited HTML/CSS feature support. | • HTMLToQPDF on GitHub |
HTML to PDF APIs | PDFBolt | Production-grade conversions with complex layouts. | External service. | • HTML to PDF API Guide |
Browser Automation | PuppeteerSharp Playwright | Pixel-perfect rendering of modern web apps. | Higher resource usage, more complex setup | • Generate 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)! ☕☕☕