Skip to main content
A full invoice, broken down. Copy the template and data below into POST /generate (or the playground) and it renders as-is. Then swap in your own data shape. It exercises most of the language: a row header, side-by-side address columns, a forEach line-item table, a totals block, and a richText page-number footer.

Template

{
  "page": {
    "size": "A4",
    "margin": 30,
    "defaultTextStyle": { "fontSize": 10 },
    "header": {
      "type": "row",
      "items": [
        { "size": { "kind": "relative", "weight": 1 }, "child": { "type": "column", "items": [
          { "type": "text", "value": "{{brand.name}}", "style": { "fontSize": 18, "bold": true } },
          { "type": "text", "value": "{{brand.tagline}}", "style": { "fontSize": 9, "color": "grey.darken1" } }
        ] } },
        { "size": { "kind": "constant", "width": 180 }, "child": { "type": "column", "items": [
          { "type": "text", "value": "INVOICE", "style": { "fontSize": 20, "bold": true }, "align": "right" },
          { "type": "text", "value": "{{invoice.number}}", "style": { "fontSize": 10, "color": "grey.darken2" }, "align": "right" },
          { "type": "text", "value": "Issued: {{invoice.issuedAt}}", "style": { "fontSize": 9 }, "align": "right" }
        ] } }
      ]
    },
    "content": {
      "type": "column",
      "spacing": 8,
      "items": [
        { "type": "row", "items": [
          { "size": { "kind": "relative", "weight": 1 }, "child": { "type": "column", "items": [
            { "type": "text", "value": "Bill to", "style": { "fontSize": 9, "color": "grey.darken1" } },
            { "type": "text", "value": "{{invoice.billTo.name}}", "style": { "bold": true } },
            { "type": "text", "value": "{{invoice.billTo.address1}}" },
            { "type": "text", "value": "{{invoice.billTo.address2}}" },
            { "type": "text", "value": "{{invoice.billTo.vat}}", "style": { "fontSize": 9 } }
          ] } },
          { "size": { "kind": "relative", "weight": 1 }, "child": { "type": "column", "items": [
            { "type": "text", "value": "Pay to", "style": { "fontSize": 9, "color": "grey.darken1" }, "align": "right" },
            { "type": "text", "value": "{{invoice.payTo.name}}", "style": { "bold": true }, "align": "right" },
            { "type": "text", "value": "{{invoice.payTo.iban}}", "align": "right" },
            { "type": "text", "value": "{{invoice.payTo.stripe}}", "style": { "fontSize": 9 }, "align": "right" }
          ] } }
        ] },
        {
          "type": "table",
          "columns": [
            { "kind": "relative", "weight": 5 },
            { "kind": "relative", "weight": 1 },
            { "kind": "relative", "weight": 2 },
            { "kind": "relative", "weight": 2 }
          ],
          "header": { "row": [
            { "child": { "type": "text", "value": "Description", "style": { "semiBold": true, "fontSize": 9 } }, "border": { "bottom": 1, "color": "grey.darken1" }, "padding": 4 },
            { "child": { "type": "text", "value": "Qty", "style": { "semiBold": true, "fontSize": 9 }, "align": "right" }, "border": { "bottom": 1, "color": "grey.darken1" }, "padding": 4 },
            { "child": { "type": "text", "value": "Unit price", "style": { "semiBold": true, "fontSize": 9 }, "align": "right" }, "border": { "bottom": 1, "color": "grey.darken1" }, "padding": 4 },
            { "child": { "type": "text", "value": "Line total", "style": { "semiBold": true, "fontSize": 9 }, "align": "right" }, "border": { "bottom": 1, "color": "grey.darken1" }, "padding": 4 }
          ] },
          "body": {
            "forEach": "invoice.lines",
            "row": [
              { "child": { "type": "text", "value": "{{item.description}}" }, "border": { "bottom": 1, "color": "grey.lighten2" }, "padding": 3 },
              { "child": { "type": "text", "value": "{{item.qty}}", "align": "right" }, "border": { "bottom": 1, "color": "grey.lighten2" }, "padding": 3 },
              { "child": { "type": "text", "value": "{{item.unitPrice}}", "align": "right" }, "border": { "bottom": 1, "color": "grey.lighten2" }, "padding": 3 },
              { "child": { "type": "text", "value": "{{item.lineTotal}}", "align": "right" }, "border": { "bottom": 1, "color": "grey.lighten2" }, "padding": 3 }
            ]
          }
        },
        { "type": "column", "items": [
          { "type": "text", "value": "Subtotal: {{invoice.subtotal}} EUR", "align": "right" },
          { "type": "text", "value": "VAT ({{invoice.vatRate}}): {{invoice.vat}} EUR", "align": "right" },
          { "type": "text", "value": "Total: {{invoice.total}} EUR", "style": { "bold": true, "fontSize": 12 }, "align": "right" }
        ] }
      ]
    },
    "footer": {
      "type": "richText",
      "align": "center",
      "spans": [
        { "value": "Page ", "style": { "fontSize": 9, "color": "grey.darken1" } },
        { "pageNumber": true },
        { "value": " of ", "style": { "fontSize": 9, "color": "grey.darken1" } },
        { "totalPages": true }
      ]
    }
  }
}

Data

{
  "brand": { "name": "Tipar", "tagline": "api.tipar.dev" },
  "invoice": {
    "number": "INV-001",
    "issuedAt": "2026-05-13",
    "vatRate": "19%",
    "subtotal": "116.91",
    "vat": "22.21",
    "total": "139.12",
    "billTo": {
      "name": "Acme Widgets SRL",
      "address1": "Str. Exemplu 12, Bl. A1",
      "address2": "Cluj-Napoca, 400000, Romania",
      "vat": "VAT: RO12345678"
    },
    "payTo": {
      "name": "Tipar SRL",
      "iban": "IBAN: RO00 TIPAR 0000 0000 0000",
      "stripe": "Stripe: cs_test_xxx"
    },
    "lines": [
      { "description": "API access — Starter plan, May 2026", "qty": 1, "unitPrice": "19.00", "lineTotal": "19.00" },
      { "description": "Overage: additional 250 documents", "qty": 1, "unitPrice": "4.75", "lineTotal": "4.75" },
      { "description": "Premium template pack — Invoices EU", "qty": 1, "unitPrice": "29.00", "lineTotal": "29.00" },
      { "description": "Priority support add-on (monthly)", "qty": 1, "unitPrice": "15.00", "lineTotal": "15.00" },
      { "description": "Discount: founding-customer credit", "qty": 1, "unitPrice": "-10.00", "lineTotal": "-10.00" }
    ]
  }
}

How it’s built

1

Header — a two-cell row

A row splits into a flexible left cell (brand) and a fixed 180 pt right cell (invoice metadata, right-aligned). Because header sits on page, it repeats if the invoice runs to a second page.
2

Addresses — two columns side by side

Another row of two equal relative cells, each a column of text. The right one is right-aligned so the two blocks frame the page.
3

Line items — a forEach table

The table has four columns. Its body.forEach is "invoice.lines", so it renders one row per line item, with {{item.…}} pulling each line’s fields. A heavier rule under the header, a hairline under each row.
4

Totals — plain text, pre-computed

Subtotal, VAT, and total are ordinary right-aligned text nodes. Tipar does no arithmetic — subtotal, vat, and total arrive already computed and formatted in data. See why.
5

Footer — page numbers

A richText footer prints Page X of Y using the pageNumber and totalPages spans, centered.
Add more entries to invoice.lines and the table grows; overflow onto a second page paginates automatically, re-drawing the header row and incrementing the footer’s page count.

More templates

Receipt, certificate, contract, and a multi-page report.