Starter invoice
A teal identity band, a billed-to block, a data-bound items table and a totals stack.
The simplest place to start: about 130 lines of template with nothing clever in it. The header is a two-column table, the line items are one table row bound to the items array, and the totals are a borderless table aligned right.
Data-bound table
Rich header cells
Footer band
1 page
render_invoice.rb
ruby
require 'json'
require 'soli/pdf'
template = JSON.parse(File.read('invoice.template.json'))
data = JSON.parse(File.read('invoice.data.json'))
pdf = Soli::PDF.render(
template: template,
data: data,
title: 'Invoice AN-2025-0042',
author: 'Atelier Nord'
)
File.binwrite('invoice.pdf', pdf)
Run it yourself
- Add the gem to your Gemfile — see Installation.
- Download invoice.template.json and invoice.data.json.
- Run
ruby render_invoice.rb.
invoice.template.json
json
{
"fonts": ["titillium"],
"options": { "header_height": 0 },
"footer": [
{
"type": "paragraph",
"value": "${company.name} · ${company.email} · Thank you for your business",
"options": { "alignment": "center", "fontSize": 8 }
}
],
"content": [
{
"type": "table",
"header_columns": [
{
"content": [
{ "type": "text", "value": "${company.name}", "fontSize": 19, "fontWeight": "bold" },
{ "type": "text", "value": "${company.tagline}", "fontSize": 9 }
],
"width": 300,
"alignment": "left",
"borderSides": { "top": "false", "bottom": "false", "left": "false", "right": "false" }
},
{
"text": "INVOICE",
"width": 181,
"fontSize": 26,
"fontWeight": "bold",
"alignment": "right",
"borderSides": { "top": "false", "bottom": "false", "left": "false", "right": "false" }
}
],
"rows": [],
"options": {
"header": { "fillColor": "0F766E", "textColor": "FFFFFF" },
"padding_x": 18,
"padding_y": 18
}
},
{ "type": "move", "y": 26 },
{
"type": "table",
"header_columns": [],
"rows": [
[
{
"content": [
{ "type": "text", "value": "BILLED TO", "fontSize": 9, "fontWeight": "bold" },
{ "type": "text", "value": "${customer.name}", "fontSize": 12, "fontWeight": "bold" },
{ "type": "text", "value": "${customer.address}", "fontSize": 10 },
{ "type": "text", "value": "${customer.city}", "fontSize": 10 }
],
"width": 300,
"alignment": "left",
"borderSides": { "top": "false", "bottom": "false", "left": "false", "right": "false" }
},
{
"content": [
{ "type": "text", "value": "Invoice no. ${invoice.number}", "fontSize": 10, "fontWeight": "bold" },
{ "type": "text", "value": "Issued ${invoice.issued}", "fontSize": 10 },
{ "type": "text", "value": "Due ${invoice.due}", "fontSize": 10 }
],
"width": 181,
"alignment": "right",
"borderSides": { "top": "false", "bottom": "false", "left": "false", "right": "false" }
}
]
],
"options": { "padding_x": 0, "padding_y": 2 }
},
{ "type": "move", "y": 26 },
{
"type": "table",
"data": "items",
"header_columns": [
{ "text": "DESCRIPTION", "width": 280, "fontSize": 9, "fontWeight": "bold", "alignment": "left", "borderSides": { "top": "false", "left": "false", "right": "false", "bottom": "true" } },
{ "text": "QTY", "width": 50, "fontSize": 9, "fontWeight": "bold", "alignment": "center","borderSides": { "top": "false", "left": "false", "right": "false", "bottom": "true" } },
{ "text": "RATE", "width": 70, "fontSize": 9, "fontWeight": "bold", "alignment": "right", "borderSides": { "top": "false", "left": "false", "right": "false", "bottom": "true" } },
{ "text": "AMOUNT", "width": 81, "fontSize": 9, "fontWeight": "bold", "alignment": "right", "borderSides": { "top": "false", "left": "false", "right": "false", "bottom": "true" } }
],
"rows": [
[
{ "text": "${name}", "width": 280, "fontSize": 10, "alignment": "left", "borderSides": { "top": "false", "left": "false", "right": "false", "bottom": "true" }, "borderColor": "EAECEF" },
{ "text": "${qty}", "width": 50, "fontSize": 10, "alignment": "center", "borderSides": { "top": "false", "left": "false", "right": "false", "bottom": "true" }, "borderColor": "EAECEF" },
{ "text": "${rate}", "width": 70, "fontSize": 10, "alignment": "right", "borderSides": { "top": "false", "left": "false", "right": "false", "bottom": "true" }, "borderColor": "EAECEF" },
{ "text": "${amount}", "width": 81, "fontSize": 10, "alignment": "right", "borderSides": { "top": "false", "left": "false", "right": "false", "bottom": "true" }, "borderColor": "EAECEF" }
]
],
"options": {
"header": { "fillColor": "E6F2F1", "textColor": "0F4F4A", "borderColor": "0F766E" },
"padding_x": 6,
"padding_y": 7
}
},
{ "type": "move", "y": 14 },
{
"type": "table",
"header_columns": [],
"rows": [
[
{ "text": "", "width": 301, "borderSides": { "top": "false", "left": "false", "right": "false", "bottom": "false" } },
{ "text": "Subtotal", "width": 100, "fontSize": 10, "alignment": "right", "borderSides": { "top": "false", "left": "false", "right": "false", "bottom": "false" } },
{ "text": "${totals.subtotal}", "width": 80, "fontSize": 10, "alignment": "right", "borderSides": { "top": "false", "left": "false", "right": "false", "bottom": "false" } }
],
[
{ "text": "", "width": 301, "borderSides": { "top": "false", "left": "false", "right": "false", "bottom": "false" } },
{ "text": "VAT (20%)", "width": 100, "fontSize": 10, "alignment": "right", "borderSides": { "top": "false", "left": "false", "right": "false", "bottom": "false" } },
{ "text": "${totals.vat}", "width": 80, "fontSize": 10, "alignment": "right", "borderSides": { "top": "false", "left": "false", "right": "false", "bottom": "false" } }
],
[
{ "text": "", "width": 301, "borderSides": { "top": "false", "left": "false", "right": "false", "bottom": "false" } },
{ "text": "Amount due", "width": 100, "fontSize": 14, "fontWeight": "bold", "alignment": "right", "borderSides": { "top": "true", "left": "false", "right": "false", "bottom": "false" }, "borderColor": "0F766E" },
{ "text": "${totals.due}", "width": 80, "fontSize": 14, "fontWeight": "bold", "alignment": "right", "borderSides": { "top": "true", "left": "false", "right": "false", "bottom": "false" }, "borderColor": "0F766E" }
]
],
"options": { "padding_x": 6, "padding_y": 6 }
},
{ "type": "move", "y": 28 },
{
"type": "table",
"header_columns": [
{
"content": [
{ "type": "text", "value": "Payment", "fontSize": 10, "fontWeight": "bold" },
{ "type": "text", "value": "${invoice.terms}", "fontSize": 10 }
],
"width": 481,
"alignment": "left",
"borderSides": { "top": "false", "bottom": "false", "left": "false", "right": "false" }
}
],
"rows": [],
"options": { "header": { "fillColor": "F4F6F6" }, "padding_x": 14, "padding_y": 12 }
}
]
}
invoice.data.json
json
{
"data": {
"company": {
"name": "Atelier Nord",
"tagline": "Design & fabrication studio",
"email": "studio@ateliernord.fr"
},
"customer": {
"name": "Maison Lumière",
"address": "14 Rue des Archives",
"city": "75004 Paris, France"
},
"invoice": {
"number": "AN-2025-0042",
"issued": "28 Nov 2025",
"due": "28 Dec 2025",
"terms": "Bank transfer within 30 days — IBAN FR76 3000 4000 0312 3456 7890 143. Late payments accrue 1.5% monthly."
},
"items": [
{ "name": "Brand identity system", "qty": 1, "rate": "€2,400", "amount": "€2,400" },
{ "name": "Signage fabrication (6 panels)", "qty": 6, "rate": "€180", "amount": "€1,080" },
{ "name": "On-site installation", "qty": 1, "rate": "€520", "amount": "€520" }
],
"totals": {
"subtotal": "€4,000",
"vat": "€800",
"due": "€4,800"
}
}
}
More in Billing