Sats docs

Template format (schemaVersion 1)

The machine-readable definition is the JSON Schema. This document explains intent. If the two disagree, the schema wins and this document has a bug.

Reference examples, each with sample data: order confirmation (data): tables across pages, totals, datasheets; FDV documentation (data): cover with QR code, table of contents, one datasheet per product, continuous page numbers.

Units and coordinates

Element lives in Coordinates are relative to Width available
section.elements the section's top-left, inside the page margins page width − left/right margin
pageTemplate.header/footer.elements the band's top-left, inside the margins same as above
pageTemplate.elements (page layer) the page's top-left corner (ignores margins) full page width

Document structure

Template
├── page          size, orientation, margin
├── fonts[]       font faces (builtin or uploaded asset)
├── styles{}      named text styles; "base" is the default
├── dataSchema    optional JSON Schema for render data (data contract)
├── sampleData    editor preview data only
└── parts[]       rendered in order
    ├── flow part
    │   ├── pageTemplates { default, first?, last? }
    │   │   ├── background   PDF page drawn behind everything
    │   │   ├── header       band of fixed height inside top margin area
    │   │   ├── footer       band of fixed height inside bottom margin area
    │   │   └── elements[]   page layer (absolute on page)
    │   └── sections[]       stacked vertically in the flow area
    │       └── elements[]   positioned inside the section
    └── pdf part      inserts pages from an existing PDF (optionally once per list item)

The flow area of a page is the page minus margins minus header height minus footer height. Header sits at the top of the margin box, footer at the bottom of it. page.margin and table padding are boxes with optional top, right, bottom, left; an absent side (or an absent box) is 0.

Page template selection per page of a flow part:

  1. first page of the part → first if present
  2. page where the flow ends → last if present (wins over first on a one-page document only if first is absent)
  3. otherwise → default

Because first and last may have different header/footer heights, the flow area can differ per page. Layout handles this: content is measured against the area of the page it lands on.

Sections

A section is a horizontal band of the flow with a base height. It contains freely positioned elements. Properties:

Property Default Meaning
repeat — Render the section once per item of a list; item available as as (default item)
visibleIf — Skip the section when the expression is falsy
keepTogether true Never split this section (or each repetition) across pages if it fits on an empty page
keepWithNext false Move to the next page together with the following section
breakBefore false Always start on a new page
tocTitle — Adds a table-of-contents entry pointing to where this section starts

A section grows when it contains a text element with fit: "grow" or a table: an element moves down by the largest growth (laid-out height minus h) of any element whose bottom edge is at or above its top, and the section grows by the largest amount any element moved. Elements next to each other do not push each other. For tables, elements below the table follow wherever the table ends, even on a later page.

Elements

Common fields: id (unique within the template), type, x, y, w, h, optional name, rotation, visibleIf, locked; showOn applies only in page templates (all, first, notFirst, last, notLast).

Type Key fields Notes
text content (with {{ }}), style, fit, minFontSize fit: clip, ellipsis, shrink (down to minFontSize), grow (sections only)
image source ({asset} or {binding}), fit, align Binding may resolve to an asset id, an https:// URL or a data: URI
rect fill, stroke, strokeWidth, radius, ellipse
line stroke, strokeWidth, dash From (x, y) to (x + w, y + h)
barcode format (qr, code128, ean13, datamatrix), value, color, showText Box includes the quiet zone; EAN-13 adds the check digit to 12 digits; showText for 1D codes
table rows, columns[], styles, repeatHeader, carryOver, zebra, borders Grows and splits across pages between rows
toc style, leader, links Entries from tocTitle on parts and sections

Table columns

width is points or a fraction ("1fr", "2fr") of the width left after fixed columns. cell, header and footer are text templates. Inside cell, the row item is in scope under the name given in rows.as. footer is rendered once after the last row (e.g. a sum).

Styles: body cells use cellStyle, then the column's style on top. Header, footer and carry-over rows use headerStyle / footerStyle (falling back to cellStyle) with the alignment of the column's style, so right-aligned number columns get right-aligned headers. Cells with wrap: true (default) wrap and make the row taller; wrap: false keeps one line with an ellipsis. Row height = max(minRowHeight, tallest cell + top and bottom padding). zebra fills every second body row. borders draw horizontal lines under rows, vertical lines between columns and an outer frame, as enabled (default colour #000000, width 0.5 pt). @index, @first and @last refer to the row inside cell.

carryOver adds a row at the bottom of each page that breaks the table ("Overføres") and a row at the top of the next page ("Overført") with running sums for the listed column ids. The label spans the columns before the first summed column. The running sum uses the column's cell expression value before formatting, so the cell must compute a number before its last filter (see Expressions).

Text styles

A style is a set of optional properties. Resolution order (later wins): styles.base → named style chain via extends → ref in an inline style → inline properties.

Properties still unset after resolution get these defaults: fontFamily = the family of the first entry in fonts (Inter when fonts is empty), fontSize 10, fontWeight 400, italic false, underline false, color #000000, align left, verticalAlign top, lineHeight 1.2, letterSpacing 0.

lineHeight is a multiple of the font size; a text box of height h holds floor(h / (fontSize × lineHeight)) lines, and fit decides what happens to the rest.

Fonts

fonts declares faces by family, weight and italic. A style's fontFamily must name a declared family or a bundled builtin family (bundled now: Inter 400/600/700, Noto Sans 400/700). The face is chosen by the CSS weight rule; a missing italic face falls back to upright. Glyphs missing in the chosen face fall back to the template's other families, then Noto Sans, then ? (warning missing_glyph).

PDF parts

Insert pages from a PDF given by source. With repeat, one insert per list item (for example one datasheet per product). pages selects pages ("all", "1-3,5"). fit: scaleToPage scales pages of another size onto the template page size; keep keeps their own size. countPages decides whether inserted pages count in @page/@pageCount. overlay is a page template drawn on top of each inserted page (typically just a footer with a page number); the repeat item is in scope there. A bound source that is empty inserts nothing. tocTitle adds one entry per insert.

Versioning

schemaVersion is an integer. Additive, optional fields do not change it. Renames, removals, changed defaults or changed semantics bump it, and the server ships a migration that upgrades stored drafts and is applied to published versions on read.