Building a Dynamic HTML-to-PDF Invoice Engine in C# (IronPDF + Templates)
As part of a recent business application I was building, I ran into a very common but surprisingly hard problem: generating professional PDF invoices that can change layout per customer.
Some customers wanted a simple invoice. Others wanted branding, custom sections, different totals placement, extra notes, or even different terminology. Hardcoding invoice layouts in C# or using report designers quickly became unmaintainable.
So I built a dynamic HTML-to-PDF invoice engine in C#, where:
- Each customer can have their own HTML invoice template
- Templates support placeholders, loops, and conditional logic
- Data comes from a strongly-typed Invoice model
- PDFs are generated using IronPDF
In this article, I’ll walk you through the complete approach, architecture, and code so you can build something similar for your own .NET applications.
The Problem with Traditional Invoice Generation
Most invoice systems start simple:
- One PDF layout
- Hardcoded columns
- Fixed structure
But real-world business software quickly demands:
- Multiple invoice layouts
- Customer-specific branding
- Optional sections (discounts, VAT, notes, bank info)
- Dynamic item rows
- Localization (labels, dates, currencies)
Using report designers or PDF drawing APIs makes these changes expensive and fragile.
HTML, however, already solves layout, styling, and responsiveness extremely well.
So the real challenge becomes:
How do we safely and flexibly turn HTML templates into PDFs in C#?
High-Level Architecture
Here’s the architecture I ended up with:
Invoice Model (C#)
↓
HTML Template (with placeholders)
↓
Template Engine (replace, loops, conditions)
↓
IronPDF (HTML → PDF)
↓
Final Invoice PDF
Each layer has a single responsibility, which keeps the system easy to extend and debug.
Designing the Invoice Model
Everything starts with a clean invoice model. This ensures templates stay readable and strongly typed.
public class Invoice
{
public string InvoiceNumber { get; set; }
public DateTime Date { get; set; }
public DateTime DueDate { get; set; }
public Party From { get; set; }
public Party To { get; set; }
public string ProjectName { get; set; }
public string Notes { get; set; }
public List<InvoiceItem> Items { get; set; }
public decimal SubTotal { get; set; }
public decimal Vat { get; set; }
public decimal Discount { get; set; }
public decimal Total { get; set; }
}
This model is what ultimately drives the HTML template.
HTML as the Invoice Layout
Instead of designing invoices in code, each invoice layout is just an HTML file.
Example snippet from an invoice template:
<div class="invoice-title">{{label:Invoice}}</div>
<div class="invoice-meta">
{{label:Number}}: {{InvoiceNumber}}<br>
{{Date}}
</div>
This gives non-developers (or frontend developers) full control over layout, CSS, spacing, and branding.
Custom Placeholders Explained
The template engine supports several placeholder types.
1️⃣ Simple value placeholders
{{InvoiceNumber}}
{{DueDate}}
{{Total}}
These map directly to properties on the invoice model.
2️⃣ Nested object placeholders
<strong>{{From.Name}}</strong><br>
{{From.AddressLine1}}
This allows clean access to nested objects like From and To.
3️⃣ Loop placeholders (invoice items)
{{#Items}}
<tr>
<td>{{Description}}</td>
<td style="text-align:right">{{Price}}</td>
</tr>
{{/Items}}
The engine repeats the block once per invoice item.
4️⃣ Label placeholders (localization-ready)
{{label:Invoice}}
{{label:DueDate}}
{{label:Total}}
Labels can be resolved from a dictionary, making the same template usable across languages.
Executing the HTML Template
The template engine is intentionally simple:
- Load HTML file
- Replace scalar placeholders
- Process loops
- Apply conditional logic
string template = File.ReadAllText(htmlPath);
var htmlContent = htmlTemplateEngine.Execute(template, invoice);
The result is pure HTML, ready for PDF rendering.
Converting HTML to PDF with IronPDF
This is where IronPDF shines.
var renderer = new ChromePdfRenderer();
renderer.RenderingOptions.CssMediaType = PdfCssMediaType.Print;
renderer.RenderingOptions.PrintHtmlBackgrounds = true;
renderer.RenderingOptions.PaperSize = PdfPaperSize.A4;
var pdf = renderer.RenderHtmlAsPdf(htmlContent);
pdf.SaveAs(savePath);
IronPDF uses a Chromium-based engine, which means:
- Modern CSS works
- Page breaks behave predictably
- Fonts and images render correctly
This makes it ideal for invoice documents.
Handling Page Breaks and Long Invoices
One issue I encountered was content being cut off vertically when invoices became long.
The fix was mostly CSS-based:
table {
page-break-inside: auto;
}
tr {
page-break-inside: avoid;
}
.invoice-page {
min-height: 100%;
}
IronPDF respects print CSS rules very well, so layout issues can usually be solved without code changes.
Why This Approach Scales Well
This system works well because:
- New invoice layouts = new HTML file
- No recompilation needed for layout changes
- Designers and developers can work independently
- Same engine supports invoices, quotations, statements
It also fits naturally into SaaS and multi-tenant systems.
GitHub Repository
The full working project is available here:
👉 https://github.com/yourusername/your-repo-name
You can clone it, modify templates, and adapt it to your own business needs.
Final Thoughts
HTML-to-PDF is one of those problems that looks easy at first and becomes complex very quickly.
By combining:
- Clean C# models
- Flexible HTML templates
- A lightweight template engine
- A robust PDF renderer like IronPDF
…I was able to build an invoice system that is flexible, maintainable, and production-ready.
If you’re building business software in .NET and struggling with document generation, this approach is well worth considering.
Resources
IronPDF – HTML to PDF for .NET
https://ironpdf.comIronPDF HTML to PDF Documentation
https://ironpdf.com/docs/
Thanks for reading — feel free to fork the repo or adapt the idea for your own projects.
Tags: #csharp #dotnet #pdf #html #opensource
Top comments (0)