Skip to main content
Three nodes do all the arranging: column stacks things vertically, row places them side by side, and spacer adds a gap. Everything else (text, table, image) is content you drop into them.

column

A vertical stack. Children render top to bottom, with an optional uniform gap between them.
{
  "type": "column",
  "spacing": 8,
  "items": [
    { "type": "text", "value": "Bill to", "style": { "color": "grey.darken1" } },
    { "type": "text", "value": "{{customer.name}}", "style": { "bold": true } },
    { "type": "text", "value": "{{customer.address}}" }
  ]
}
FieldTypeRequiredNotes
itemsNode[]yesThe children, in order.
spacingnumber (points)noGap between successive items. Omit for none.
column is the workhorse — most page bodies are a column with spacing and a handful of items.

row

A horizontal band split into sized cells. Each item has a size and a child.
{
  "type": "row",
  "items": [
    { "size": { "kind": "relative", "weight": 1 }, "child": { "type": "text", "value": "{{company.name}}", "style": { "bold": true } } },
    { "size": { "kind": "constant", "width": 180 }, "child": { "type": "text", "value": "INVOICE", "align": "right" } }
  ]
}
Each items[] entry has:
FieldTypeRequiredNotes
sizesizing objectyesHow wide this cell is — see below.
childNodeyesWhat goes in the cell.

Sizing a cell

A cell’s size is one of two kinds:
{ "kind": "relative", "weight": 2 }
The cell takes a share of the remaining width, proportional to its weight. Two cells with weights 1 and 2 split the row one-third / two-thirds. Use relative sizing for content that should flex with the page.
Mixing the two is the common case: a constant cell for the thing that must be a fixed width, and a relative cell with weight: 1 next to it to soak up the rest.
A two-column body — bill-to on the left, pay-to on the right — is just a row of two relative cells, each holding a column:
{
  "type": "row",
  "items": [
    { "size": { "kind": "relative", "weight": 1 }, "child": { "type": "column", "items": [
      { "type": "text", "value": "Bill to", "style": { "color": "grey.darken1" } },
      { "type": "text", "value": "{{billTo.name}}", "style": { "bold": true } }
    ] } },
    { "size": { "kind": "relative", "weight": 1 }, "child": { "type": "column", "items": [
      { "type": "text", "value": "Pay to", "style": { "color": "grey.darken1" }, "align": "right" },
      { "type": "text", "value": "{{payTo.name}}", "style": { "bold": true }, "align": "right" }
    ] } }
  ]
}

spacer

A fixed vertical gap. Useful inside a column when you want more (or less) space than uniform spacing gives, or to push content down a page — certificates lean on it heavily.
{ "type": "spacer", "size": 24 }
FieldTypeRequiredNotes
sizenumber (points)yesHeight of the gap. Must be ≥ 0.
For horizontal space between row cells, don’t use a spacer — adjust the cell size/weight instead, or add padding if it’s a table cell.

Putting it together

Layout nodes nest without limit. A typical page body is a column whose items are some rows, a table, and a few spacers:
{
  "type": "column",
  "spacing": 14,
  "items": [
    { "type": "row", "items": [ "...header band..." ] },
    { "type": "table", "...": "..." },
    { "type": "spacer", "size": 8 },
    { "type": "text", "value": "Total due: {{invoice.total}}", "style": { "bold": true }, "align": "right" }
  ]
}

Next: Text & styling

Style the text you place into these layouts.