Skip to content

Fields and data

A field is the part of a template that changes from one document to the next. Everything else is fixed design. Choosing the fields well is half of designing a template: a field too many makes the data hard to produce, a field too few makes the template impossible to reuse.

A field lives on the element that shows it, as its placeholder:

"placeholder": {
"id": "3b9f6c20-1000-4000-8000-000000000101",
"name": "Event title",
"code": "title",
"placeholderType": "text",
"required": true,
"description": "The name of the event, at most two lines."
}
  • code is the key the data uses: the value of title in the data fills this element. It must start with a letter or _ and contain only letters, digits and _ (at most 128 characters), and it must be unique in the template. Use short, meaningful English snake_case: title, hero_photo, price, products.
  • name and description are for people and agents: say what the value is and its constraints.
  • required says whether a document can be rendered without it. defaultValue (always written as a string) is used when no value arrives.
placeholderType Goes on The value in the data
text a text a string
number a text a JSON number (12, 89.5), printed with the field’s format
boolean a text, or a container true or false: prints a label, or shows the element only when true
image an image an HTTPS URL, a Madoo storage path or a data:image/… URI
color a text or a shape #RRGGBB, applied to the fill or the stroke
json a repeated list a JSON array of objects, one per row

A field on the wrong kind of element is rejected (DESIGN_PLACEHOLDER_TYPE_MISMATCH).

The data of a document is one JSON object keyed by field code. For the event poster:

{
"title": "Books in the courtyard",
"dates": "Every Wednesday in June, 9 pm",
"photo": "https://images.example.com/courtyard.jpg",
"intro": "Five summer evenings in the library courtyard…",
"speakers": [
{ "date": "Wed 3 June", "line": "Laura Benassi — The water houses" },
{ "date": "Wed 10 June", "line": "Tommaso Rinaldi — The miller's dog" }
],
"price": 12,
"sold_out": false,
"event_id": "courtyard-2026"
}

Values are checked before anything is drawn:

  • an unknown code or a value of the wrong JSON type is an error;
  • a required field with no value and no default stops the render with DESIGN_PLACEHOLDER_REQUIRED and a path that names it — title, or speakers[1].line for a key of a list item;
  • an optional field with no value leaves its element empty (or hidden, for a yes/no field in visibility mode).

Keep facts and generated text apart in the data when a workflow fills the template: prices, names and dates should come from verified sources, and AI-written copy from its own object — the workflow nodes can merge several data objects so that facts always win. See Templates in workflows.

A json field feeds a repeated list (repeat_region): the region draws its row once per item of the array.

  • The region’s sourceCode is the list field’s code (speakers), and the region carries that json field.
  • Each element inside the row carries its own field with a bindingPath item.<key> — item.date, item.line — that reads a key of the current item. Its code just needs to be unique; the key is what matters. Nested keys are allowed (item.author.name).
  • The keys the row reads form the item contract. Keys that only a condition or a link reads can be declared in the region’s itemFields so they are part of the contract too.
  • maxItems (1–100) caps the list; overflowPolicy decides what happens when the items do not fit: fail, fit (shrink the rows), clip (drop what does not fit) or continue_page (add pages). Lists have their own technique page.

A list inside an item — the tags of each dish, the features of each product — is read by a list nested in the row with "sourceCode": "item.features" and no field of its own; inside it, item. refers to the inner item. Lists nest two levels deep. A nested list that carries a json field with a bindingPath is rejected (DESIGN_BINDING_INVALID: A JSON repeat source must be a root placeholder). See Repeated lists.

A number field prints its value through a format:

"format": {
"locale": "en-GB", "numberStyle": "currency", "currency": "GBP", "currencyDisplay": "symbol",
"minimumFractionDigits": 0, "maximumFractionDigits": 2,
"prefix": "Tickets from "
}

Always write currencyDisplay in a currency format ("symbol" for £, €, $; "code" for GBP, EUR, USD). A format that uses none of the options currencyDisplay, useGrouping, signDisplay, negative, prefix, suffix or a {language} locale prints with the original rules of the first Madoo templates — GBP 49.00 instead of £49.00 — so that templates published before these options existed never change. The editor always writes currencyDisplay; a document you write must too.

  • locale decides separators and currency placement (it-IT prints 12,50 €, en-US prints $12.50); {language} takes the locale from the data key language, so one template serves several markets.
  • numberStyle is decimal, currency or percent (percent takes a fraction: 0.25 prints 25%).
  • currency (ISO code) and currencyDisplay (symbol or code); minimumFractionDigits and maximumFractionDigits (0–20); useGrouping for thousands separators (turn it off for a year); signDisplay and negative (minus or parentheses).
  • prefix and suffix (up to 24 characters) add fixed text around the number: 13.5% vol, 75 cl, 85 m².

Always send numbers as numbers and let the template format them: the same value can then print differently on a price list and on a receipt, and a later change of format needs no change in the data.

  • A boolean field prints trueLabel or falseLabel (default Yes / No), or, with "booleanMode": "visibility", shows its element — a badge, a seal, a whole panel — only when the value is true. The sold out badge of the event poster works this way.
  • A color field paints its text or shape with the color in the data; colorTarget chooses fill or stroke. One template can then carry each brand’s color.

Any element can carry a condition: it prints only when the condition holds. When it does not, the element is not drawn, and inside a flowing Layout it leaves no gap.

"condition": { "operator": "all", "conditions": [
{ "operator": "greater_than", "placeholderCode": "item.stock", "literal": "0" },
{ "operator": "less_than_or_equal", "placeholderCode": "item.stock", "literal": "3" }
] }

Operators: not_empty, equals, greater_than, greater_than_or_equal, less_than, less_than_or_equal (with a numeric literal), not (one condition), all and any (several). A condition reads a template field by code, or an item key (item.stock) inside a list row. Conditions nest up to 8 levels.

Use conditions for what the data decides: a last pieces badge, a logo shown only if there is one, an optional quote, an empty-list message.

Any element can be a link in the PDF: "link": { "href": "https://shop.example.com/p/{item.sku}", "description": "Open the product" }.

  • Allowed schemes: https, http, mailto, tel.
  • Braces read values and are URL-encoded: {code} reads a template field, {item.key} a key of the current list item. A link outside a list can only read fields the template has; inside a row it may read an item key that the row does not print (the key joins the item contract).
  • A link that resolves to nothing usable is left out, and the render reports it. Links exist only in the PDF: page images are not clickable, and print PDFs (PDF/X-4) omit them.

When a template is published, its fields become an immutable contract: every field with its code, type, required flag and default, and for each list the keys of its items. Workflows, Apps and agents read the contract of a revision (get_design_template view contract) to know exactly what to send. A later revision may change the contract; workflows keep the revision they were built with until someone moves them on.

A sample set is a named set of values stored in the template. Sample sets are how a template is tested: the exact preview renders each of them as it will print, and publishing checks them all.

Write sample sets that cover the cases the template must survive, not only the pleasant one:

  • the longest realistic values — a two-line title, the longest product name, the most list items;
  • the shortest — one item, an empty optional field, a missing logo;
  • each branch — a product in stock, one in its last pieces, one sold out;
  • each market — a second language or locale, if the template serves more than one.

A required field that no sample set exercises is flagged when you check the template before publishing.