Back to invoice templates
Cookbook

Recipes for invoice templates

Copy-ready BlockTemplate snippets for recurring tasks — from SEPA QR codes to dynamic VAT rows. Drop them into the body.blocks array (or header / footer).

Open recipe book with snippet cards (QR, discount, logo, page number)
01

SEPA giro code (QR) for bank transfers

A qrcode block with EPC-069-12 compliant content sourced from the nested seller.bankAccount (iban is required there — and mandatory for ZUGFeRD/XRechnung with SEPA transfer anyway). Customers scan it in their banking app and transfer details are pre-filled.

SEPA giro code (QR) for bank transfers
json
1{
2 "type": "qrcode",
3 "condition": "{{seller.bankAccount.iban}}",
4 "data": {
5 "size": 120,
6 "alignment": "right",
7 "eccLevel": "M",
8 "content": "BCD\n002\n1\nSCT\n{{seller.bankAccount.bic}}\n{{seller.bankAccount.accountHolder}}\n{{seller.bankAccount.iban}}\nEUR{{total}}\n\n{{invoiceNumber}}\n\nRechnung {{invoiceNumber}}"
9 }
10}
02

Article-number column in the line-items table

An extra column for the article / service number from items[].articleNumber. When the field is empty, the cell simply stays blank.

Article-number column in the line-items table
json
1{
2 "type": "table",
3 "data": {
4 "dataSource": "{{items}}",
5 "columns": [
6 { "field": "articleNumber", "header": "Art. No.", "width": "15%" },
7 { "field": "description", "header": "Description", "width": "*" },
8 { "field": "quantity", "header": "Qty", "width": "10%", "align": "center" },
9 { "field": "unitPrice", "header": "Unit", "width": "15%", "align": "right", "format": "currency" },
10 { "field": "netAmount", "header": "Net", "width": "15%", "align": "right", "format": "currency" }
11 ]
12 }
13}
03

Logo on the right, company on the left in the header

Two-column header with the sender address on the left and the logo on the right — repeatOnAllPages turns it into a letterhead. The logo is embedded via a static URL or a Base64 data URI.

Logo on the right, company on the left in the header
json
1{
2 "type": "table",
3 "data": {
4 "dataSource": "{{items}}",
5 "columns": [
6 { "field": "articleNumber", "header": "Artikel-Nr.", "width": "15%" },
7 { "field": "description", "header": "Beschreibung", "width": "*" },
8 { "field": "quantity", "header": "Menge", "width": "10%", "align": "center" },
9 { "field": "unitPrice", "header": "Einzel", "width": "15%", "align": "right", "format": "currency" },
10 { "field": "netAmount", "header": "Netto", "width": "15%", "align": "right", "format": "currency" }
11 ]
12 }
13}
04

Page number in the footer

The runtime placeholders {{pageNumber}} and {{pageCount}} provide the current page and total page count. Combine with repeatOnAllPages.

Page number in the footer
json
1{
2 "footer": {
3 "repeatOnAllPages": true,
4 "blocks": [
5 { "type": "line", "data": { "thickness": 0.5, "color": { "token": "lineColor" } } },
6 { "type": "text", "data": {
7 "content": "Page {{pageNumber}} of {{pageCount}} • Invoice {{invoiceNumber}}",
8 "fontSize": 8,
9 "alignment": "center",
10 "color": { "token": "mutedColor" }
11 }}
12 ]
13 }
14}
05

Dynamic VAT rows from taxSummary

One summary row per tax rate — no hard-wired 19% logic. Works for mixed invoices with 7%, 19%, reverse charge (AE) or exempt (Z/E).

Dynamic VAT rows from taxSummary
json
1{
2 "type": "summary",
3 "data": {
4 "alignment": "right",
5 "labelBold": true,
6 "headerRows": [
7 { "label": "Net total", "value": "{{subtotal}}", "valueFormat": "currency" }
8 ],
9 "dynamicRows": {
10 "dataSource": "{{taxSummary}}",
11 "valueField": "taxAmount",
12 "valueFormat": "currency",
13 "labelTemplate": "VAT {{taxRate}}% ({{taxCategoryCode}})"
14 },
15 "footerRows": [
16 { "label": "Grand total", "value": "{{total}}", "bold": true, "valueFormat": "currency", "separator": true }
17 ]
18 }
19}
06

Payment terms with early-payment discount

Conditional block using paymentTerms.earlyPaymentDiscount (nested). Renders only when discountPercent is set.

Payment terms with early-payment discount
json
1{
2 "type": "text",
3 "condition": "{{paymentTerms.earlyPaymentDiscount.discountPercent}}",
4 "data": {
5 "content": "Payable by {{dueDate}}. Pay within {{paymentTerms.earlyPaymentDiscount.days}} days and get a {{paymentTerms.earlyPaymentDiscount.discountPercent}}% early-payment discount.",
6 "fontSize": 9,
7 "margin": [0, 12, 0, 0]
8 }
9}
07

Small-business note (§ 19 UStG)

countrySpecific.isKleinunternehmer is a boolean flag for DE invoices. The statutory note is rendered only when the flag is set.

Small-business note (§ 19 UStG)
json
1{
2 "type": "text",
3 "condition": "{{countrySpecific.isKleinunternehmer}}",
4 "data": {
5 "content": "Pursuant to § 19 UStG, no VAT is charged (German small-business rule).",
6 "italics": true,
7 "fontSize": 9,
8 "margin": [0, 12, 0, 0]
9 }
10}
08

B2G: Leitweg-ID in the header

For invoices to public-sector buyers (DE). Leitweg-ID and buyer reference live under countrySpecific. Payload note: countrySpecific requires the discriminator countryCode: "DE", and EN 16931 expects at least one of leitwegId or buyerReference.

B2G: Leitweg-ID in the header
json
1{
2 "type": "keyvalue",
3 "condition": "{{countrySpecific.leitwegId}}",
4 "data": {
5 "layout": "horizontal",
6 "labelWidth": 120,
7 "labelBold": true,
8 "items": [
9 { "label": "Leitweg-ID", "value": "{{countrySpecific.leitwegId}}" },
10 { "label": "Buyer reference", "value": "{{countrySpecific.buyerReference}}", "optionalMode": "anyEmpty" }
11 ]
12 }
13}

Missing a recipe?

Drop us a line — we're happy to add frequently requested patterns to the cookbook.

Suggest a recipe →