Docs menu
TemplatesTemplate format
Template format
The JSON document behind every template, for building and changing templates in code.
A template is one JSON document. The template editor writes it; GET /v1/templates/{id} and the MCP tool get_template return it as body; you can change it and send it back with PUT or update_template. Markdown and HTML make a template too.
Lengths are in points: 72 points make an inch, 2.835 a millimeter. Keys this reference does not name are ignored.
Example#
An A4 invoice: a title with a field, a due date in the English date format, a table with one row per record of the lines list, a total from a formula, page numbers in the footer and a logo from the data on page 1.
The second block is the data it reads. The logo is shortened here; a real one is the whole base64 text.
{
"version": 0,
"page": { "width": 595.28, "height": 841.89,
"margins": { "top": 72, "bottom": 60, "left": 56, "right": 56 } },
"base": { "font": "sans", "size": 10.5 },
"bands": {
"footer": { "blocks": [ { "spans": [ { "text": "Page " }, { "text": "", "auto": "page" },
{ "text": " of " }, { "text": "", "auto": "pages" } ],
"style": { "align": "right", "size": 8 } } ] }
},
"blocks": [
{ "spans": [ { "text": "Invoice ", "style": { "bold": true, "size": 20 } },
{ "text": "", "field": "invoice.number", "style": { "bold": true, "size": 20 } } ] },
{ "spans": [ { "text": "Due " },
{ "text": "", "field": "invoice.due", "format": { "kind": "date", "locale": "en" } } ],
"style": { "spaceAfter": 12 } },
{ "type": "table", "headerRepeat": true,
"columns": [ { "weight": 3 }, { "weight": 1 }, { "weight": 1 } ],
"tableStyle": { "borderColor": "#d2d6dc", "borderWidth": 0.5, "headerBackground": "#eef2f7", "padding": 5 },
"rows": [
{ "kind": "header", "cells": [ { "spans": [ { "text": "Item" } ] },
{ "spans": [ { "text": "Qty" } ] },
{ "spans": [ { "text": "Price" } ] } ] },
{ "kind": "body", "cells": [ { "spans": [ { "text": "" } ] },
{ "spans": [ { "text": "" } ], "style": { "align": "right" } },
{ "spans": [ { "text": "" } ], "style": { "align": "right" } } ] } ],
"bind": { "source": "lines",
"columns": [ { "key": "item" }, { "key": "qty", "format": { "kind": "number" } },
{ "key": "price", "format": { "kind": "currency", "currency": "EUR" } } ] } },
{ "spans": [ { "text": "Total " },
{ "text": "", "formula": "SUM(lines.price)", "format": { "kind": "currency", "currency": "EUR" } } ],
"style": { "align": "right", "bold": true } }
],
"placed": [
{ "type": "image", "field": "company.logo", "x": 440, "y": 30, "width": 100, "height": 40, "pages": "first", "fit": "contain" }
]
}{ "invoice": { "number": "2026-0147", "due": "2026-11-13" },
"company": { "logo": "data:image/png;base64,iVBORw0KGgo…" },
"lines": [ { "item": "Toner cartridge", "qty": 2, "price": 9.5 } ] }Page#
| Name | Type | Description |
|---|---|---|
versionrequired | 0 | The format version. |
page.width, page.heightrequired | number | A4 is 595.28 × 841.89, Letter 612 × 792. |
page.marginsrequired | object | top, bottom, left, right: the body area. The bands sit in the margins. |
page.first.margins | object | Page 1's own top and bottom, for a letterhead. |
base | Style | The style every paragraph starts from. |
blocksrequired | array | The body: paragraphs, tables and graphics, flowing from page to page. |
bands | object | Text repeated on every page. See Bands. |
placed | array | Boxes at a fixed place. See Placed boxes. |
layout | object | The editor's page grid. See Grid and label sheets. |
id | string | Blocks, rows and placed boxes may carry an id. The editor uses it for its undo steps. It changes nothing in the PDF. |
Paragraphs and spans#
A paragraph is {"spans": [...], "style": {...}}; type is paragraph or left out. A span is a run of text with one style:
| Name | Type | Description |
|---|---|---|
textrequired | string | The text. Empty for a field, a formula or a page number. A line break (\n) starts a new line. |
field | string | A data path such as customer.name: the value replaces the text. |
formula | string | An Excel formula such as SUM(lines.price): its value replaces the text. See Formulas. |
format | object | How a field's or a formula's value is written. See Fields and formats. |
fallback | string | The text shown when the data has no value (missing, null or empty). A field with a fallback is optional. |
auto | "page" | "pages" | The page number or the page count. In bands and placed boxes only. |
style | Style | The span's own style, over the paragraph's. |
Style#
A paragraph's style sets its layout and its text; a span's style sets its text only.
| Name | Type | Description |
|---|---|---|
font | string | sans (Noto Sans, the default), serif, mono, or a family of the font list. Another name is refused. |
size | number | Points. |
color, background | string | #rrggbb. |
bold, italic, underline, strike | boolean | A font without bold or italic faces gets them drawn. |
align | string | left, right, center or justify. |
lineHeight | number | A multiple of the size. |
spaceBefore, spaceAfter, indent | number | Points. |
keepWithNext, keepTogether, breakBefore | boolean | Page breaks: keep the paragraph on the next one's page, keep its lines together, start it on a new page. |
Fields and formats#
A field reads a path of the JSON data: invoice.number reads {"invoice": {"number": …}}. Without a format, the value is written as it is.
A field the data does not fill fails the render, unless the span has a fallback.
For a long date, use a formula: TEXT(invoice.date, "mmmm d, yyyy") gives October 28, 2026, and TEXT(invoice.date, "d mmmm yyyy") gives 28 October 2026. dddd writes the weekday and ddd its short name: TEXT(invoice.date, "dddd, mmmm d, yyyy") gives Wednesday, October 28, 2026. d and dd stay the day of the month.
| Name | Type | Description |
|---|---|---|
kindrequired | string | text, number, currency or date. A date value is yyyy-mm-dd. |
decimals | integer | For number (default 0) and currency (default 2). |
currency | string | An ISO code. EUR, USD and GBP print €, $ and £, RON prints lei; other codes, such as CHF and JPY, print the code. Default EUR. |
locale | string | en, en-US, de, fr or ro: separators, the currency position and the date pattern. |
Formulas and conditions#
A formula is Excel's formula language without the =. A data path is a name: invoice.total * 1.2, SUM(lines.price). A path to a list gives a list, so COUNTA(lines.item) counts the records. TODAY() is the render date.
when on a block or a placed box is a formula: the block or box shows only when it is true. A number is true unless 0; a blank is false.
The functions: ABS, AND, AVERAGE, AVERAGEIF, CEILING, CONCAT, CONCATENATE, COUNT, COUNTA, COUNTBLANK, COUNTIF, COUNTIFS, DATE, DATEDIF, DAY, DAYS, DROP, EDATE, EOMONTH, EXACT, FALSE, FILTER, FIND, FIXED, FLOOR, IF, IFERROR, IFNA, IFS, INDEX, INT, ISBLANK, ISERROR, ISNUMBER, ISTEXT, LEFT, LEN, LOWER, MAX, MID, MIN, MOD, MONTH, NETWORKDAYS, NOT, OR, PI, POWER, PRODUCT, PROPER, REPLACE, REPT, RIGHT, ROUND, ROUNDDOWN, ROUNDUP, ROWS, SEARCH, SIGN, SORT, SQRT, SUBSTITUTE, SUM, SUMIF, SUMIFS, SUMPRODUCT, SWITCH, TAKE, TEXT, TEXTJOIN, TODAY, TRIM, TRUE, TRUNC, UNIQUE, UPPER, VALUE, WEEKDAY, XOR, YEAR.
Repeated blocks#
repeat on a block shows it, and the blocks it covers, once per record of a list. Each item of the list must be a record: a list of plain strings is refused. Write such a list as records ([{"text": "…"}]), or join it in one paragraph with TEXTJOIN(", ", TRUE, skills).
| Name | Type | Description |
|---|---|---|
repeat.sourcerequired | string | A JSON path to a list of records, or csv:<name>. |
repeat.span | integer | The blocks the repeat covers, this one first. Default 1. |
repeat.as | string | The name the covered blocks read the record by. Default item: a field item.qty reads the record's qty. |
Tables#
A table is a block with "type": "table". Its body rows can be written in the template, or come from data with bind: one body row per record, each cell styled as the template's first body row.
| Name | Type | Description |
|---|---|---|
columnsrequired | array | Per column, width in points or a weight that shares the free width. |
rowsrequired | array | Each row has kind (header, body or footer) and cells, and keepWithNext to keep it on the next row's page. A cell has spans, a style, and colSpan and rowSpan for merged cells; a row lists only the cells that start in it. |
headerRepeat | boolean | Repeat the header rows on each page the table runs onto. |
minRowsPerPage | integer | Rows to keep together at a page break. |
tableStyle | object | borderColor, borderWidth, headerBackground, bandBackground (striped rows), padding. |
bind.source | string | A JSON path to a list of records, such as lines, or csv:<name> for CSV data. |
bind.columns | array | Per table column, the record key, or a formula on the row's record (item.qty * item.price), and an optional format. A column with neither ({}) fills its cell of the first body row once per record: the cell's spans keep their own styles, and their fields and formulas read the record as item, such as a bold item.role and a gray item.company in one cell. |
bind.sort | object | by (a record key) and desc: the rows in that order, smallest first unless desc is true. |
bind.filter | string | A formula on the row's record: a row shows only when it is true. |
style, when, repeat | object, string, object | As on a paragraph. |
Bands#
bands holds text repeated on every page, in the margins: header, footer, left and right (the side labels: the left one reads upward, the right one downward). Each is {"blocks": [paragraphs]}. bands.first holds page 1's own bands; a band it leaves out falls back to the others. inset and gap set the distance from the page edge and from the body, in points (default 24 and 6).
Placed boxes#
placed lists boxes at a fixed place on the page, over the body: text boxes, colored boxes, ellipses, lines, images and graphics.
| Name | Type | Description |
|---|---|---|
typerequired | string | text, rect, ellipse, line, image or graphic. |
x, y, width, heightrequired | number | From the top left corner of the page, in points. A line runs from (x, y) to (x + width, y + height). |
pages | string | all, first or a page number such as "2". Default first. |
blocks, padding | array, number | A text box's paragraphs and the space around them. |
fill, stroke, strokeWidth | string, string, number | The box's background and border. |
src | string | An image's file as a PNG or JPEG data URL. |
field | string | An image's data path: the data gives the image, as a PNG or JPEG data URL of at most 4 MB. A link is refused; nothing is fetched. See Images. |
fit | string | An image: contain keeps its proportions inside the box, centered; fill (the default) stretches it to the box. |
aspect | number | An image's width divided by its height. With fit: "contain", a data URL gives its own size and needs none. |
graphic | object | A graphic box's content. See Graphics. |
when | string | A formula: the box shows only when it is true. |
slot | string | The grid cell the box fills. See Grid and label sheets. |
Graphics#
A graphic is a barcode or QR code, math, a diagram or chart, code or SVG. It sits in the text flow as a block, {"type": "graphic", "graphic": {…}, "width": 120, "height": 120, "align": "center"} (align is left, center or right; the box never splits), or at a fixed place as a placed box of type graphic. In a source or a barcode value, ${path} puts in a field's value and ${=FORMULA} the result of a formula.
The barcode types: qr, microqr, rmqr, ean13, ean8, upca, upce, code128, itf14, itf, code39, code93, codabar, msi, databar, databar-limited, databar-expanded, datamatrix, pdf417, aztec, maxicode.
| Name | Type | Description |
|---|---|---|
kindrequired | string | barcode, math, mermaid, svg, code: mermaid draws diagrams and charts. |
source | string | The math (LaTeX), diagram (Mermaid), code or SVG text. |
lang | string | Code: the language to highlight. |
barcode | object | type (one of the barcode types), value, and options such as ec, module-shape, module-color, background, label, gs1. A linear barcode prints its value under the bars: label: none hides it, other label text replaces it, and label-position is below, above, inside-bottom or inside-top. A value with line breaks, as an EPC payment needs, is drawn plain and takes no options. |
chart | object | A chart drawn from a list: type (bar, line, pie), list (a JSON path or csv:<name>), label and value (the record keys for the names and the numbers), and an optional title. Each record gives one bar, point or slice; bars and lines start at zero. It replaces source. |
Grid and label sheets#
layout is the editor's page grid: inset (points from the page edge), an optional top (points from the page top where the grid starts, below a page header) and a root cell. A cell has an id, and either children side by side (dir: "row") or stacked ("column"), or none. size shares the free space, fixed sets a length in points, gap separates children, and one cell may be body: true: the text flow's area. Placed boxes name their cell in slot; the editor keeps their place and size equal to the cell's.
layout.repeat.source (a JSON path or csv:<name>) makes a label sheet: each cell takes the next record, left to right and top to bottom, and pages repeat until the records run out. A cell with no box of its own repeats the boxes of the first cell that has some. Its boxes read the record as item and its number as index.
Fonts#
sans, serif and mono, and these families: Roboto, Open Sans, Lato, Montserrat, Inter, Poppins, Source Sans 3, Raleway, Nunito, Work Sans, Rubik, IBM Plex Sans, PT Sans, Fira Sans, Barlow, Merriweather, Lora, Playfair Display, PT Serif, Libre Baskerville, EB Garamond, Source Serif 4, Crimson Text, Roboto Slab, Cormorant Garamond, Bitter, IBM Plex Serif, Spectral, Libre Caslon Text, Oswald, Bebas Neue, Anton, Abril Fatface, Archivo Black, Alfa Slab One, Righteous, Lobster, Josefin Sans, Dancing Script, Pacifico, Caveat, Great Vibes, Satisfy, Indie Flower, JetBrains Mono, Fira Code, Source Code Pro, IBM Plex Mono, Roboto Mono, Space Mono.
Limits#
A template is at most 2 MB of JSON, its images included.
The plan sets how many templates an account keeps; GET /v1/templates and list_templates say how many.
The plan's page and time limits apply to each render.