Programmatic invoice generation is the process of creating PDF invoices automatically from structured data (JSON) and an HTML template, using a rendering engine or API. Instead of manually filling in a Word document, your application provides the data and the system produces a pixel-perfect PDF in 200-400ms.
Every business sends invoices. Most start manually: a Google Docs template, copy-pasting client names and line items. That works for five invoices a month. At fifty, it becomes a bottleneck. At five hundred, it's unsustainable.
| Approach | Setup time | Best for | Rendering engine |
|---|---|---|---|
| PDF API (PDF4.dev) | Minutes | Production workloads, any language | Chromium (hosted) |
| Headless browser (Playwright) | Hours | Full control, self-hosted | Chromium (local) |
| PDF library (pdf-lib, PDFKit) | Hours | Simple docs, no browser dependency | Programmatic |
How much does automated invoicing save?
Automated invoice generation reduces processing costs by 78% and processing time by 82%. According to Ardent Partners' 2025 AP benchmark, the average cost to process a single invoice manually is $12.88. Best-in-class automated teams bring that down to $2.88. Processing time drops from 17.4 days to 3.1 days.
Beyond cost, automation solves three recurring problems:
- Consistency. Every invoice follows the same layout, the same number format, the same tax calculation. No more "this invoice uses a different font" or "the subtotal formula is broken."
- Speed. An API call takes 200-400ms. Generating and emailing an invoice can happen the moment a payment is confirmed or a project milestone is completed.
- Auditability. Every generated PDF is logged with a timestamp, template version, and the exact data used. Recreating any invoice months later takes one database query.
What is the best approach for programmatic invoice generation?
The most reliable approach is HTML templates combined with a PDF rendering engine. You write the invoice layout in HTML/CSS, inject dynamic data using a template engine like Handlebars, and render the result to PDF with a headless browser (Playwright) or a PDF API (PDF4.dev).
This gives you:
- Full CSS control over typography, colors, spacing, and layout
- Handlebars variables for dynamic fields (
{{invoice_number}},{{client_name}},{{#each items}}) - Pixel-perfect output because the PDF is rendered by a real browser engine (Chromium)
| Step | Input | Output |
|---|---|---|
| 1. Design | HTML template with CSS layout | Invoice structure (header, table, totals) |
| 2. Inject | JSON data (company, items, amounts) | Compiled HTML with real values |
| 3. Render | Compiled HTML → Chromium | PDF file (A4, pixel-perfect) |
How to design an HTML invoice template
A well-structured invoice template separates layout from data completely. The HTML defines the visual structure (company header, line items table, totals), while Handlebars variables ({{company_name}}, {{#each items}}) mark where dynamic data gets injected at render time. Here is a complete template:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<style>
/* Reset + base typography */
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: 'Inter', Arial, sans-serif; font-size: 14px; color: #1a1a1a; }
/* Layout: flexbox header with company info left, invoice meta right */
.header { display: flex; justify-content: space-between; margin-bottom: 48px; }
.invoice-number { font-size: 28px; font-weight: 700; color: #7c3aed; }
/* Client section: light gray box */
.client-section { background: #f9fafb; border-radius: 8px; padding: 20px; margin-bottom: 32px; }
/* Line items table */
table { width: 100%; border-collapse: collapse; }
th { text-align: left; border-bottom: 2px solid #e5e7eb; padding: 12px 16px; }
td { padding: 14px 16px; border-bottom: 1px solid #f0f0f0; }
/* Totals: right-aligned summary */
.totals-row.total { border-top: 2px solid #1a1a1a; font-weight: 700; font-size: 18px; }
</style>
</head>
<body>
<!-- Company header -->
<div class="header">
<div>
<div style="font-size: 22px; font-weight: 700;">{{company_name}}</div>
<div>{{company_address}}</div>
</div>
<div>
<div class="invoice-number">#{{invoice_number}}</div>
<div>Issued: {{issue_date}} / Due: {{due_date}}</div>
</div>
</div>
<!-- Bill to -->
<div class="client-section">
<div style="font-weight: 600;">{{client_name}}</div>
<div>{{client_address}}</div>
</div>
<!-- Line items with Handlebars #each loop -->
<table>
<thead>
<tr><th>Description</th><th>Qty</th><th>Unit price</th><th>Amount</th></tr>
</thead>
<tbody>
{{#each items}}
<tr>
<td>{{description}}</td>
<td>{{quantity}}</td>
<td>{{formatCurrency unit_price ../currency ../locale}}</td>
<td>{{formatCurrency amount ../currency ../locale}}</td>
</tr>
{{/each}}
</tbody>
</table>
<!-- Totals with conditional tax and discount -->
<div class="totals">
<div>
<div class="totals-row">Subtotal: {{formatCurrency subtotal currency locale}}</div>
{{#if tax_rate}}
<div class="totals-row">Tax ({{tax_rate}}%): {{formatCurrency tax_amount currency locale}}</div>
{{/if}}
{{#if discount}}
<div class="totals-row">Discount: -{{formatCurrency discount currency locale}}</div>
{{/if}}
<div class="totals-row total">Total: {{formatCurrency total currency locale}}</div>
</div>
</div>
{{#if notes}}
<div class="notes"><strong>Notes:</strong> {{notes}}</div>
{{/if}}
</body>
</html>The key Handlebars features used here: {{#each items}} iterates over line items, {{#if tax_rate}} conditionally shows tax, and {{formatCurrency amount currency locale}} formats numbers according to locale (e.g. "$1,500.00" for en-US, "1 500,00 €" for fr-FR).
How to generate the invoice with code
Send a POST request to the render endpoint with your template ID and invoice data. The API returns the PDF as a binary response. Here are examples in six languages:
const response = await fetch('https://pdf4.dev/api/v1/render', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.PDF4_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
template_id: 'invoice',
data: {
company_name: 'Acme Corp',
invoice_number: 'INV-2026-042',
issue_date: '2026-03-11',
due_date: '2026-04-10',
client_name: 'Globex Industries',
currency: 'USD',
locale: 'en-US',
items: [
{ description: 'Web development', quantity: 40, unit_price: 150, amount: 6000 },
{ description: 'Design review', quantity: 8, unit_price: 175, amount: 1400 },
],
subtotal: 7400,
tax_rate: 8.25,
tax_amount: 610.50,
total: 8010.50,
},
}),
});
const pdf = Buffer.from(await response.arrayBuffer());
fs.writeFileSync('invoice.pdf', pdf);How to reuse headers and footers across invoices
PDF4.dev's component system lets you create a header or footer once and attach it to every template. The component repeats on every printed page automatically using CSS paged media, which is essential for multi-page invoices with many line items. Components are HTML fragments that inherit Handlebars variables from the parent template.
<!-- Header component (created once in the dashboard) -->
<div style="display: flex; justify-content: space-between; align-items: center; padding-bottom: 16px; border-bottom: 2px solid #7c3aed;">
<div style="font-size: 18px; font-weight: 700;">{{company_name}}</div>
<div style="font-size: 11px; color: #999;">Invoice #{{invoice_number}}</div>
</div>In your template HTML, reference it with a single tag:
<pdf4-header component-id="comp_your_header_id">Company Header</pdf4-header>Variables like {{company_name}} and {{invoice_number}} resolve automatically because the component inherits the parent template's data context.
What Handlebars helpers are available for invoices?
PDF4.dev registers built-in Handlebars helpers for number, currency, and date formatting. These helpers use the browser's Intl API, so they automatically respect locale conventions for decimal separators, currency symbols, and date formats. No manual formatting logic needed in your templates.
| Helper | Example | Output |
|---|---|---|
formatCurrency | {{formatCurrency 1500.50 "USD" "en-US"}} | $1,500.50 |
formatCurrency | {{formatCurrency 1500.50 "EUR" "fr-FR"}} | 1 500,50 € |
formatDate | {{formatDate issue_date "long"}} | March 11, 2026 |
formatNumber | {{formatNumber 1234.5 "en-US"}} | 1,234.5 |
math | {{math quantity "*" unit_price}} | Computed product |
padStart | {{padStart invoice_number 6 "0"}} | 000042 |
How to embed a company logo in a generated PDF
Base64-encode your logo and embed it directly in the HTML template. External image URLs can fail during rendering if the network is slow or the CDN is down. Base64 embedding guarantees the logo appears in every PDF, regardless of the rendering environment.
<img src="data:image/png;base64,iVBORw0KGgo..." alt="Company logo" style="height: 40px;" />This adds a few KB to the template size but eliminates network dependency entirely.
How to handle page breaks in multi-page invoices
CSS paged media properties control where page breaks occur in the PDF output. Use page-break-inside: avoid on table rows to prevent line items from splitting across pages:
tbody tr {
page-break-inside: avoid;
}
.notes {
page-break-before: auto;
page-break-inside: avoid;
}The totals section should ideally stay on the same page as the last few items. Wrapping the totals in a container with page-break-inside: avoid helps keep them together.
How to support multiple currencies in invoice templates
Use the formatCurrency Handlebars helper with a currency code variable instead of hardcoding $ or € symbols. The helper delegates to the browser's Intl.NumberFormat API, which handles symbol placement, decimal separators, and grouping for any ISO 4217 currency code:
<td>{{formatCurrency amount currency locale}}</td>Your data object then controls the formatting:
{ "currency": "JPY", "locale": "ja-JP", "amount": 150000 }This renders as "¥150,000" without changing the template.
How to integrate invoice generation into your application
How to generate an invoice on payment confirmation
The most common trigger for invoice generation is a successful payment. When a Stripe webhook confirms a payment, your server generates the PDF and emails it to the client in a single request cycle. Here is a complete Express.js example:
// After Stripe webhook confirms payment
app.post('/webhooks/stripe', async (req, res) => {
const event = req.body;
if (event.type === 'payment_intent.succeeded') {
const payment = event.data.object;
const order = await getOrder(payment.metadata.order_id);
const pdf = await fetch('https://pdf4.dev/api/v1/render', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.PDF4_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
template_id: 'invoice',
data: buildInvoiceData(order),
}),
});
const buffer = Buffer.from(await pdf.arrayBuffer());
await sendEmail({
to: order.client_email,
subject: `Invoice #${order.invoice_number}`,
attachments: [{ filename: `invoice-${order.invoice_number}.pdf`, content: buffer }],
});
}
res.sendStatus(200);
});How to generate invoices in bulk
Batch invoice generation uses Promise.allSettled to parallelize API calls. A typical render takes 200-400ms, so generating 100 invoices in parallel completes in under 10 seconds. For monthly billing cycles:
async function generateMonthlyInvoices(orders: Order[]) {
const results = await Promise.allSettled(
orders.map(async (order) => {
const response = await fetch('https://pdf4.dev/api/v1/render', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.PDF4_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
template_id: 'invoice',
data: buildInvoiceData(order),
}),
});
if (!response.ok) {
const error = await response.json();
throw new Error(`Invoice ${order.id}: ${error.error.message}`);
}
return {
orderId: order.id,
pdf: Buffer.from(await response.arrayBuffer()),
};
})
);
const succeeded = results.filter(r => r.status === 'fulfilled');
const failed = results.filter(r => r.status === 'rejected');
console.log(`Generated ${succeeded.length} invoices, ${failed.length} failed`);
return { succeeded, failed };
}How to generate invoices with AI agents (MCP)
MCP (Model Context Protocol) is an open standard that lets AI agents call external tools directly. PDF4.dev exposes a render_pdf MCP tool that Claude, Cursor, and other AI-powered tools can call to generate invoices conversationally through PDF4.dev's MCP server:
"Generate an invoice for Globex Industries, 40 hours of web development at $150/hour, due in 30 days."
The AI agent calls the render_pdf tool with the right template and data. No code needed.
What are the e-invoicing compliance requirements?
E-invoicing is the exchange of invoice documents in a structured electronic format between suppliers and buyers. It is becoming mandatory in many jurisdictions. The EU Directive 2014/55/EU already requires all public contracting authorities in the EU to accept electronic invoices. The ViDA (VAT in the Digital Age) regulation, adopted by the EU Council in March 2025, will make e-invoicing mandatory for all intra-EU B2B transactions by July 1, 2030.
Over 50 countries worldwide have adopted or are adopting e-invoicing mandates. The Peppol network, a cross-border e-invoicing infrastructure, now connects over 2.5 million organizations across 111 countries.
Are PDF invoices legally valid?
Plain PDF invoices remain legally valid in most jurisdictions for B2B transactions. Structured e-invoicing formats (UBL, CII, Peppol BIS) go further by embedding machine-readable XML data that accounting systems can import without manual data entry.
ZUGFeRD is a hybrid invoice format that embeds a CII XML file inside a PDF/A-3 document. The related Factur-X standard (used in France) follows the same approach. The recipient gets a human-readable PDF and a machine-readable XML in the same file. Building your invoice pipeline with clean data separation (HTML template + JSON data) positions you well for these formats.
What format should invoice PDFs use for long-term archival?
PDF/A is an ISO-standardized format (ISO 19005) designed for long-term document preservation. PDF/A files are fully self-contained: all fonts are embedded, external dependencies are prohibited, and JavaScript is not allowed. If you need to store invoices for regulatory retention periods (typically 7-10 years), PDF/A guarantees the document renders identically decades from now.
PDF/A-3 is the variant most commonly used for invoices because it allows embedding additional files (like structured XML data) inside the PDF, which is required by ZUGFeRD and Factur-X.
How to secure generated PDF invoices
Invoices contain sensitive financial data: client details, bank account numbers, pricing. Three measures protect generated invoices:
- Password protection encrypts the PDF file so only the recipient can open it. See how to password protect a PDF for methods ranging from browser-based (RC4-128) to AES-256 command line encryption with qpdf.
- HTTPS only for all API calls. PDF4.dev enforces TLS on all endpoints, so invoice data is encrypted in transit.
- API key scoping limits what each key can do. Use
render_onlyscope for keys that only need to generate PDFs, reducing exposure if a key is compromised.
Should you self-host or use a PDF API?
For most teams, a PDF API is the better choice once you move past prototyping. Self-hosting Playwright gives you full control but requires managing Chromium in Docker, handling concurrency, and monitoring for browser crashes. The Node.js PDF generation guide covers the full self-hosted setup including Docker, browser pooling, and performance tuning.
| Self-hosted (Playwright) | PDF API (PDF4.dev) | |
|---|---|---|
| Setup time | Hours (Docker, Chromium deps, browser pool) | Minutes (API key + fetch call) |
| Monthly invoices < 100 | Fine, single server handles it | Easier, but either works |
| Monthly invoices > 1,000 | You manage concurrency, crashes, scaling | Handled for you |
| Template editing | Redeploy on every change | Visual editor, instant updates |
| Docker image impact | +300-400 MB for Chromium | None |
For most teams, the API approach wins once you pass the prototype stage. You trade a small per-PDF cost for zero infrastructure maintenance.
PDF4.dev includes a built-in invoice starter template. Sign up free and generate your first invoice in under two minutes.
FAQ
What is the best way to generate PDF invoices programmatically?
Use an HTML template with Handlebars variables for dynamic data (company name, line items, totals), then render it to PDF with a headless browser or a PDF API like PDF4.dev. This gives you full CSS control over the design while keeping the data pipeline simple.
Can I generate invoices in bulk?
Yes. With an API-based approach, you send one HTTP request per invoice. A typical render takes 200-400ms, so generating 100 invoices sequentially takes under a minute. For higher throughput, parallelize requests with Promise.allSettled or equivalent.
How do I add a company logo to a generated invoice?
Embed the logo as a base64-encoded image in your HTML template. This avoids external network requests during rendering and ensures the logo appears reliably in every PDF, regardless of the environment.
Is it legal to send PDF invoices instead of paper?
In most jurisdictions, yes. The EU Directive 2014/55/EU mandates e-invoicing for public procurement, and the ViDA regulation will require e-invoicing for all intra-EU B2B transactions by 2030. Most countries outside the EU also accept electronic invoices with proper record-keeping.
How do I handle multi-page invoices with many line items?
HTML-to-PDF rendering handles pagination automatically. The content flows across pages just like in a browser print preview. Use CSS page-break-inside: avoid on table rows to prevent items from splitting across pages.
Can I password protect generated invoices?
Yes. Generate the PDF first, then encrypt it before delivery. You can use qpdf for AES-256 encryption, or PDF4.dev's Protect PDF tool for browser-based protection. See the full guide on password protecting PDFs.
What format should invoice PDFs use for archival?
PDF/A (ISO 19005) is the standard for long-term document archival. PDF/A-3 is commonly used for invoices because it allows embedding structured data (XML) alongside the visual PDF, which is required by formats like ZUGFeRD and Factur-X.
Free tools mentioned:
Start generating PDFs
Build PDF templates with a visual editor. Render them via API from any language in ~300ms.