Skip to main content
A template is a JSON document that describes a PDF page. You send it to POST /generate alongside a separate data object; Tipar walks the template, substitutes your data, and lays out the page.
{
  "template": { "page": { "...": "..." } },
  "data": { "...": "..." }
}

Two rules

Everything about the template language follows from two deliberate constraints.
Templates only do {{path}} substitution against data. There is no arithmetic, no formatting helpers, no conditionals (beyond repeating table rows). Compute subtotals, totals, dates, and currency strings in your code and pass the finished values in data. This keeps templates safe to accept from anywhere and keeps rendering fast and predictable.
Nodes are layout primitives — column, row, text, table — not semantic components like invoice or lineItem. You compose a document the way you’d compose a layout. It’s a little more verbose than a purpose-built invoice block, but it’s fully general: the same handful of nodes build invoices, certificates, contracts, and reports.

Anatomy of a template

A template has exactly one top-level key, page:
{
  "page": {
    "size": "A4",
    "margin": 30,
    "defaultTextStyle": { "fontSize": 10 },
    "header":  { "type": "..." },
    "content": { "type": "..." },
    "footer":  { "type": "..." }
  }
}
FieldTypeRequiredNotes
size"A4" · "A3" · "Letter" · "Legal"noDefault A4.
marginnumber (points)noDefault 30. Applied uniformly to all four sides.
defaultTextStyleStylenoPage-wide text defaults. See the propagation note below.
headerNodenoRepeats at the top of every page.
contentNodeyesThe page body — the only required field.
footerNodenoRepeats at the bottom of every page.
All measurements are in points (1 point = 1/72 inch), the standard PDF unit. A4 is 595 × 842 pt; Letter is 612 × 792 pt.
defaultTextStyle only propagates fontSize and color. The weight and slant flags (bold, semiBold, italic) apply only where you set them on a node’s own style — so a page default can’t accidentally bold your entire document.

The node catalog

Every node carries a "type" discriminator. There are seven node types:

column

Vertical stack of child nodes.

row

Horizontal band of sized cells.

text

A single styled string.

richText

Mixed-style runs and page-number tokens.

spacer

A fixed vertical gap.

image

A base64-embedded image.

table

A grid with a forEach-driven body.
Nodes nest freely: a row cell can hold a column, which holds text and a table, and so on.

Interpolation

Anywhere a text value or rich-text span appears, you can interpolate data with double braces:
{ "type": "text", "value": "Invoice {{invoice.number}} for {{customer.name}}" }
  • Dotted paths walk objects: {{invoice.billTo.name}} reads data.invoice.billTo.name.
  • Whitespace inside the braces is fine: {{ invoice.number }} is the same as {{invoice.number}}.
  • Inside a table body, the current row element is bound to item{{item.description}}. See Tables.
  • Values must be scalars — strings, numbers, booleans. A path that points at an object or array renders as empty.
There is no array indexing and no expressions. {{lines[0].name}}, {{total * 1.19}}, and {{upper customer.name}} are not supported. If you need a value, compute it in your code and put it in data. To repeat content over an array, use a table.

Missing data is an error

If a template references a path that isn’t present in data, the request fails with 422 Unprocessable Entity and lists every missing path — so you fix them all in one round trip rather than one at a time.
{
  "title": "Template references missing data",
  "status": 422,
  "errors": [
    { "code": "template.missing_data", "message": "invoice.total" },
    { "code": "template.missing_data", "message": "customer.name" }
  ]
}
This is deliberate: a silently blank field on a customer’s invoice is worse than a loud failure you catch in testing. See the error reference.

Next

Layout

Stack and arrange content with column, row, and spacer.

Text & styling

Typography, colour, alignment, and page numbers.

Tables

Repeat rows over your data.

Full schema

The exhaustive node-by-node reference.