Skip to main content
This is the exhaustive reference for the template JSON. For a gentler walk-through, start with Template basics and the guides; come here for the precise contract.
Status: the v1 schema is implemented and live behind both /generate and the playground. It’s pre-1.0 and may still grow (additively); it isn’t frozen as a versioned public contract yet.
Two principles bound the design: no expressions (only {{path}} substitution — compute values in your code) and nodes mirror layout, not meaning (column, row, text, … — not invoice, lineItem). See Template basics.

Request envelope

Success is 200 application/pdf. Failures are Problem Details.

page

Nodes

Every node has a "type" discriminator. There are seven types.

column

Vertical stack. → guide: Layout

row

Horizontal stack of sized cells. → guide: Layout

text

A single styled string. → guide: Text & styling

richText

Multi-span text with mixed styling and page-number tokens. → guide: Text & styling
A Span is one of:
  • { "value": string, "style"?: Style } — an interpolatable, styled text run.
  • { "pageNumber": true } — the current page number.
  • { "totalPages": true } — the total page count.

spacer

A fixed vertical gap. → guide: Layout

image

A base64-embedded image. → guide: Images

table

A grid with an optional header and a forEach-driven body. → guide: Tables
Inside body.row, the current array element binds to item — e.g. {{item.description}}. A Cell is:

Style

All fields optional. A node’s style merges over page.defaultTextStyle. fontSize must be > 0 when present. → guide: Text & styling

Colours

A colour is either:
  • a hex string — #RGB, #RRGGBB, or #AARRGGBB (3, 6, or 8 hex digits; case-insensitive — in the 8-digit form the alpha byte comes first, ARGB), or
  • a dotted palette token (case-insensitive) from QuestPDF’s Material palette — "grey.darken1", "blue.medium", "red.lighten2", "indigo.darken3".
The resolver walks the palette path against QuestPDF’s colour set; an unknown token fails validation with 422. Full palette: the QuestPDF colour reference. → guide: Text & styling

Interpolation

Tokens are {{path.to.value}}; whitespace inside the braces is allowed.
  • Paths resolve against data{{customer.name}}, {{invoice.total}}.
  • Inside table.body.forEach, the element binds to item{{item.unitPrice}}.
  • No array indexing (lines[0].name) and no expressions. Put computed values in data.
  • A missing path fails with 422 and lists every missing path.
→ guide: Template basics

Errors

All failures are RFC 7807 Problem Details (application/problem+json) with a top-level errors array of { code, message }. Full reference: Errors. 422 codes: Example (missing data):
Example (structural):

Out of scope (v1)

Not supported, by design — deferred to a later schema, gated by customer demand:
  • QR codes, barcodes, signature fields, charts
  • Custom font upload, URL-sourced images
  • Expressions, math, formatting helpers ({{currency total}})
  • Loops or conditionals outside table.body.forEach
  • Saved/named templates — every request inlines its template
  • Array indexing in paths (lines[0]) and a literal {{ in output