Skip to content
Start free

Table

A table has a header row, and a row for each entry of a list in your data. It continues onto the next page when it doesn’t fit. Tables can’t go in a header or footer.

PropertyRequiredDescription
sourceYesThe path to the list, such as line_items, or order.items inside a repeating section over order
asYesThe name each entry gets in the cells, such as item (Lists)
columnsYes1 to 30 columns
headerNo{ "font", "background", "repeat" }: the header row’s style
rowsNo{ "font", "background", "alternateBackground", "keepTogether", "condition" }: the rows’ style, and which entries get one
sortNoUp to 3 { "by", "order" } rules for the rows’ order (Sorting). In the list’s order when left out
groupsNo{ "by", "header", "background", "font" }: rows with the same value go together under a heading row (Groups)
footerNo{ "font", "background" }: the style of the last row, when a column has a footer (Footer row)
borderNoA stroke around and between the cells
cellPaddingNo{ "top", "right", "bottom", "left" }. 2 points above and below and 4 at the sides when left out

A table grows with its rows, whatever its height: in a free section, its height is the least height of its box. Put a table in a stack, so the elements after it move down as it grows.

PropertyRequiredDescription
idYesUnique in the layout
headerYesThe column’s heading, as rich text
cellYesEach row’s cell, as rich text that reads the entry: item.description
widthNoThe column’s share of the table’s width. 1 when left out
alignNoleft, center or right, for the heading and the cells
footerNoThe column’s cell in the last row, as rich text that reads the data

Shares are relative: columns with widths 3, 1 and 1 take three fifths, one fifth and one fifth of the table.

  • header.repeat (on when left out) repeats the header row at the top of each page the table continues onto.
  • rows.alternateBackground gives every second row its own background, starting with the second row.
  • rows.keepTogether (on when left out) keeps each row on one page.
  • rows.condition gives only the entries it’s true for a row, such as item.quantity > 0 (Conditions).
  • font styles the header row’s or the rows’ text (Fonts).

Each rule in sort has by, an expression that reads the entry (item.date), and order, asc (the default) or desc. The rows are in order of the first rule’s value, then the second’s for rows where the first is the same, and so on; rows where every value is the same keep the list’s order.

  • Numbers sort by size, and so does text of a number: 1,250, $40 and 12% sort as 1250, 40 and 12. Commas only separate thousands, so 1,5 is text.
  • Text sorts alphabetically, ignoring case and accents (Émile sorts with Emile). Numbers come before text.
  • desc reverses the whole order, so text comes before numbers. Blank values go last, in either order.
  • Dates are text, so they sort in date order only when they’re written year first. Sort by item.date | date: "%Y-%m-%d" for dates written another way.

groups.by is an expression that reads the entry, such as item.category, or item.date | date: "%B %Y" for a group for each month. Rows with the same value go together, under a heading row across the table. The groups are in the order of their first rows, so sort by the same value first to put the groups in order.

PropertyRequiredDescription
byYesThe value rows are grouped by
headerYesThe heading row’s text, as rich text that reads the group’s first entry: item.category
backgroundNoThe heading row’s background
fontNoThe heading row’s text style (Fonts)

rows.alternateBackground starts again in each group.

When a column has a footer, the table ends with a row of them. The footer is outside the rows, so it reads the data rather than an entry, such as a total: line_items | sum: "amount" | currency. It reads the whole list, with the entries rows.condition leaves out.

sum reads amounts written as text the way sorting does: "1,250.50", "$40" and "12%" count as 1250.5, 40 and 12. A blank amount, or an entry without one, adds nothing. Any other text ("twelve", "1,5") fails the submission with a message naming the value, so a total is never wrong.

Line items with a quantity and an amount, a grey header that repeats on each page, and striped rows:

{
"id": "line_items_table",
"type": "table",
"width": null,
"height": null,
"source": "line_items",
"as": "item",
"columns": [
{
"id": "description",
"width": 4,
"header": {
"type": "doc",
"content": [
{
"type": "paragraph",
"content": [{ "type": "text", "text": "Description" }]
}
]
},
"cell": {
"type": "doc",
"content": [
{
"type": "paragraph",
"content": [
{ "type": "field", "attrs": { "expression": "item.description" } }
]
}
]
}
},
{
"id": "quantity",
"width": 1,
"align": "right",
"header": {
"type": "doc",
"content": [
{
"type": "paragraph",
"content": [{ "type": "text", "text": "Qty" }]
}
]
},
"cell": {
"type": "doc",
"content": [
{
"type": "paragraph",
"content": [
{ "type": "field", "attrs": { "expression": "item.quantity" } }
]
}
]
}
},
{
"id": "amount",
"width": 1.5,
"align": "right",
"header": {
"type": "doc",
"content": [
{
"type": "paragraph",
"content": [{ "type": "text", "text": "Amount" }]
}
]
},
"cell": {
"type": "doc",
"content": [
{
"type": "paragraph",
"content": [
{
"type": "field",
"attrs": { "expression": "item.amount | currency" }
}
]
}
]
}
}
],
"header": {
"background": "#eeeeee",
"font": { "color": "#333333" },
"repeat": true
},
"rows": { "alternateBackground": "#f8f8f8", "keepTogether": true },
"border": { "width": 0.5, "color": "#cccccc" },
"cellPadding": { "top": 4, "right": 6, "bottom": 4, "left": 6 }
}

Expenses grouped by category, the largest first in each group, leaving out entries without an amount, with a total in the last row:

{
"id": "expenses_table",
"type": "table",
"width": null,
"height": null,
"source": "expenses",
"as": "item",
"columns": [
{
"id": "expense",
"width": 3,
"header": {
"type": "doc",
"content": [
{
"type": "paragraph",
"content": [{ "type": "text", "text": "Expense" }]
}
]
},
"cell": {
"type": "doc",
"content": [
{
"type": "paragraph",
"content": [
{ "type": "field", "attrs": { "expression": "item.description" } }
]
}
]
}
},
{
"id": "expense_amount",
"width": 1,
"align": "right",
"header": {
"type": "doc",
"content": [
{
"type": "paragraph",
"content": [{ "type": "text", "text": "Amount" }]
}
]
},
"cell": {
"type": "doc",
"content": [
{
"type": "paragraph",
"content": [
{
"type": "field",
"attrs": { "expression": "item.amount | currency" }
}
]
}
]
},
"footer": {
"type": "doc",
"content": [
{
"type": "paragraph",
"content": [
{
"type": "field",
"attrs": {
"expression": "expenses | sum: \"amount\" | currency"
},
"marks": [{ "type": "bold" }]
}
]
}
]
}
}
],
"sort": [{ "by": "item.category" }, { "by": "item.amount", "order": "desc" }],
"groups": {
"by": "item.category",
"background": "#f3f4f6",
"header": {
"type": "doc",
"content": [
{
"type": "paragraph",
"content": [
{
"type": "field",
"attrs": { "expression": "item.category" },
"marks": [{ "type": "bold" }]
}
]
}
]
}
},
"rows": { "condition": "item.amount > 0" },
"border": { "width": 0.5, "color": "#cccccc" }
}