Starter quote
The starter invoice's skeleton with its own identity and a valid-until strip.
A good second template to read: the same structure as the starter invoice, different colours and a different call to action.
Data-bound table
Colour band
1 page
render_quote.rb
ruby
require 'json'
require 'soli/pdf'
template = JSON.parse(File.read('quote.template.json'))
data = JSON.parse(File.read('quote.data.json'))
pdf = Soli::PDF.render(
template: template,
data: data,
title: 'Quote Q-2025-0117'
)
File.binwrite('quote.pdf', pdf)
Run it yourself
- Add the gem to your Gemfile — see Installation.
- Download quote.template.json and quote.data.json.
- Run
ruby render_quote.rb.
quote.template.json
json
{
"fonts": ["titillium"],
"options": { "header_height": 0 },
"footer": [
{ "type": "paragraph", "value": "${company.name} · ${company.email} · Page #PAGE# of #TOTAL_PAGE#", "options": { "alignment": "center", "fontSize": 8 } }
],
"content": [
{
"type": "table",
"header_columns": [
{
"content": [
{ "type": "text", "value": "${company.name}", "fontSize": 19, "fontWeight": "bold" },
{ "type": "text", "value": "Estimate", "fontSize": 10 }
],
"width": 300, "alignment": "left",
"borderSides": { "top": "false", "bottom": "false", "left": "false", "right": "false" }
},
{
"text": "QUOTE", "width": 181, "fontSize": 26, "fontWeight": "bold", "alignment": "right",
"borderSides": { "top": "false", "bottom": "false", "left": "false", "right": "false" }
}
],
"rows": [],
"options": { "header": { "fillColor": "1F2430", "textColor": "E7A33E" }, "padding_x": 18, "padding_y": 18 }
},
{
"type": "table",
"header_columns": [
{
"text": "Quote ${quote.number} · Prepared for ${customer.name} · Valid until ${quote.valid_until}",
"width": 481, "fontSize": 10, "fontWeight": "bold", "alignment": "center",
"borderSides": { "top": "false", "bottom": "false", "left": "false", "right": "false" }
}
],
"rows": [],
"options": { "header": { "fillColor": "FBEFD6", "textColor": "7A5310" }, "padding_x": 12, "padding_y": 9 }
},
{ "type": "move", "y": 24 },
{
"type": "table",
"data": "items",
"header_columns": [
{ "text": "SCOPE", "width": 300, "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": "EST.", "width": 81, "fontSize": 9, "fontWeight": "bold", "alignment": "right", "borderSides": { "top": "false", "left": "false", "right": "false", "bottom": "true" } }
],
"rows": [
[
{ "text": "${name}", "width": 300, "fontSize": 10, "alignment": "left", "borderSides": { "top": "false", "left": "false", "right": "false", "bottom": "true" }, "borderColor": "EAE7E0" },
{ "text": "${qty}", "width": 50, "fontSize": 10, "alignment": "center", "borderSides": { "top": "false", "left": "false", "right": "false", "bottom": "true" }, "borderColor": "EAE7E0" },
{ "text": "${rate}", "width": 70, "fontSize": 10, "alignment": "right", "borderSides": { "top": "false", "left": "false", "right": "false", "bottom": "true" }, "borderColor": "EAE7E0" },
{ "text": "${amount}", "width": 81, "fontSize": 10, "alignment": "right", "borderSides": { "top": "false", "left": "false", "right": "false", "bottom": "true" }, "borderColor": "EAE7E0" }
]
],
"options": { "header": { "fillColor": "F4F1EA", "textColor": "1F2430", "borderColor": "1F2430" }, "padding_x": 6, "padding_y": 7 }
},
{ "type": "move", "y": 12 },
{
"type": "table",
"header_columns": [],
"rows": [
[
{ "text": "", "width": 271, "borderSides": { "top": "false", "left": "false", "right": "false", "bottom": "false" } },
{ "text": "Estimated total", "width": 130, "fontSize": 14, "fontWeight": "bold", "alignment": "right", "borderSides": { "top": "true", "left": "false", "right": "false", "bottom": "false" }, "borderColor": "E7A33E" },
{ "text": "${quote.total}", "width": 80, "fontSize": 14, "fontWeight": "bold", "alignment": "right", "borderSides": { "top": "true", "left": "false", "right": "false", "bottom": "false" }, "borderColor": "E7A33E" }
]
],
"options": { "padding_x": 6, "padding_y": 7 }
},
{ "type": "move", "y": 22 },
{
"type": "paragraph",
"value": "${quote.note}",
"options": { "fontSize": 10, "alignment": "left" }
}
]
}
quote.data.json
json
{
"data": {
"company": { "name": "Atelier Nord", "email": "studio@ateliernord.fr" },
"customer": { "name": "Maison Lumière" },
"quote": {
"number": "Q-2025-0117",
"valid_until": "31 Dec 2025",
"total": "€4,800",
"note": "This estimate is indicative and excludes shipping. Acceptance by reply email converts it into a firm order; a 30% deposit schedules fabrication."
},
"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" }
]
}
}
More in Business documents