Pages and fields
A page step and its fields, the field kinds, and fields that show only when they fit.
A page step asks the person. It holds at least one field, and a good page has at most five.
cta is the button’s label.
{
"id": "about", "type": "page", "title": "What is it about?", "cta": "Write it",
"fields": [
{ "id": "topic", "label": "Topic", "kind": "textarea", "required": true,
"placeholder": "e.g. We open our new studio on Friday", "help": "For the first line" },
{ "id": "tone", "label": "Tone", "kind": "select",
"options": ["Casual", "Professional"], "default": "Casual" }
]
}A field’s id is unique in the whole wizard, because later steps read the answer by it. Every
field has a label and a kind, and may have required, placeholder, help and default.
Field kinds
| Kind | What the person gives | Keys of its own |
|---|---|---|
text, textarea | A line, or a few paragraphs | scan (text): fill it from a QR code or barcode |
number, date, email, url | A number, a date, an address, a link | |
select, multiselect | One or several of options | optionsFrom, cards |
toggle | Yes or no | |
color | A colour | |
image | Photos, taken or uploaded | multiple, min, max (up to 30) |
file | Documents, photos, recordings | multiple, min, max, camera, video, listen |
items | Line items with totals | columns, vat, currency |
location | Where they are, or an address they type | |
audio | A voice note of up to 5 minutes, written down before the next step | |
signature | A signature drawn with the finger | |
slot | One appointment time, shown by day | options or optionsFrom, as ISO 8601 date-times |
connection | Connects one of their accounts | connection: the id of a wizard connection |
list | Checks and corrects a stored list | list: the id of a wizard list |
Uploads
The runtime checks each file an image or file field receives, from the run page or a runner
plugin’s runs.upload, before it keeps it. A space’s files go through the same check. Up to
15 MB per file, 40 MB for a clip.
| Check | What happens |
|---|---|
| Name | Folders, control characters and direction overrides go. At most 120 characters |
| Type | The first bytes must fit the declared type: a picture, a recording or clip, a PDF, an Office file, a font, text. Otherwise the upload is refused with 400. A picture is kept under the type its bytes show |
| Size | A picture of more than 50 megapixels or 16 384 pixels on a side is refused before it is decoded |
| Pictures | PNG, JPEG, WebP and AVIF are decoded and encoded again in their own format, in a separate process: no EXIF, XMP or GPS, nothing after the picture’s end, upright as the EXIF orientation said. Animated pictures, GIF, HEIC, BMP and ICO keep their bytes |
| SVG | Written again from an allowlist of drawing elements. Scripts, foreignObject, animations, event handlers and references that don’t start with # go. A DOCTYPE or entity declaration is refused |
A type the runtime doesn’t check (application/octet-stream, for example) is kept and served
only as a download. The plan with what comes next is docs/plans/upload-checks.md.
Line items
An items field takes positions and computes the totals itself:
{ "id": "positions", "label": "Positions", "kind": "items", "currency": "EUR",
"vat": { "rate": 20 },
"columns": [
{ "id": "what", "label": "Service", "kind": "text" },
{ "id": "hours", "label": "Hours", "kind": "number" },
{ "id": "rate", "label": "Rate", "kind": "money" }
] }A row’s amount is the product of its number and money columns. vat.field reads the rate
from another field instead. Documents take the table with its totals from {{positions}}. See
Templates.
Answers from earlier steps
prefill("steps.<id>.<key>"): what the field starts with. Anitemsfield takes a table whose columns are its column ids, and the person corrects the rows.optionsFrom: the choices of aselect,multiselectorslotcome from earlier data. It names a list output (steps.<id>.<key>), a column of a table output (steps.<id>.<key>.<column>) or a column of a stored list (lists.<id>.<column>).optionsstand in while that data is empty.cards: withoptionsFromnaming a table output, each row is a card. Its keys name the table’s columns:label,value(default: the label),image,subtitle,metaandlink.
Fields that show only when they fit
when shows a field only while a condition holds. It reads a field of the same page, decided as
the person types, or anything known before the page:
{ "id": "company", "label": "Company", "kind": "text",
"when": { "field": "customerType", "op": "equals", "value": "Business" } }A hidden field is not asked and not kept. Its template is empty. Conditions are described on Branches and loops.
Where no rule on one field can say it, a decision picks the fields as the run reaches the page:
askon a field: a statement. The field shows when the decision finds it true of the run, for example “The complaint is about a damaged article”.groupson the page andgroupon fields: of a group’s fields the decision shows one. Withoptional, it may show none.
"groups": [{ "id": "reach", "instructions": "How should we reach the person?" }],
"fields": [
{ "id": "phone", "label": "Phone", "kind": "text", "group": "reach" },
{ "id": "mail", "label": "Email", "kind": "email", "group": "reach" }
]A page with decided fields holds up to 12 fields and shows at most 5. A field has ask or
group, not both.