soli-pdf

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
Page 1
Page 1 of 1
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

  1. Add the gem to your Gemfile — see Installation.
  2. Download invoice.template.json and invoice.data.json.
  3. 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"
    }
  }
}