Skip to content

Schema reference

This is the contract between your backend and the engine. Anything here can change without a frontend deploy.

{
"id": "quote-form",
"fields": [ ]
}

Open in playground

PropertyTypeRequiredNotes
fieldsFieldSchema[]yesThe node tree. Containers nest arbitrarily; leaves are inputs.
idstring | numbernoPassed back to onFormLoad. Useful for multi-step flows.
anything elseunknownnoPassed through untouched — a title, a version, a note.

Every node is a FieldSchema. A node with a fields array is a container — it groups and lays out children but holds no value. A node without one is a leaf: a real input that stores a value.

Form data stays flat regardless of nesting, so every leaf name must be unique across the whole schema. The parser warns on duplicates.

PropertyTypeRequiredNotes
namestringyesThe key in form data. Unique across the whole schema.
typestringyesLooked up in your field registry. Unregistered types warn and are skipped rather than breaking the page.
ordernumberyesAscending sort within the node’s siblings. Re-order a form by changing numbers.
labelstringnoYour component decides how to render it.
valueunknownnoInitial value. For a repeater, an array of row objects.
columnGridnumbernoLayout hint passed to your component (e.g. 1–12). The engine doesn’t interpret it.

Each takes a boolean or an expression string. Expressions are re-evaluated whenever a referenced field changes.

PropertyTypeNotes
showstringHidden fields unmount, and their validation rules unregister with them. On a container, the whole subtree goes.
requiredboolean | stringDrives isRequired from useBlockField. Pair with a required validator to actually block submit.
disabledboolean | stringDrives isDisabled.
calculateValuestringDerives the value from other fields. See calculated values.
PropertyTypeNotes
validationValidationRule[]Applied in order.

A ValidationRule:

PropertyTypeNotes
namestringA built-in, or anything you registered on the validator registry.
valueunknownArgument for parameterised rules (minLength: 3).
messagestringShown when the rule fails.
validateOn'change' | 'blur' | 'submit'Defaults to 'change'.

Built-in rules: required, minLength, maxLength, min, max, length, range, email, url, pattern.

When each mode runs: on change, only change rules. On blur, change and blur rules. On submit, all rules regardless of setting. Use blur for async server checks so they don’t fire on every keystroke.

A validator is a function returning an error message or null, and it may return a Promise. It receives the whole form data, so cross-field rules need no wiring in your components:

const validators = createValidatorRegistry()
validators.register('unique', async (value) => {
const taken = await api.checkUsername(String(value))
return taken ? 'Already taken' : null
})
validators.register('matches', (otherField) => (value, formData) =>
value === formData?.[String(otherField)] ? null : `Doesn't match ${otherField}`,
)

Pass the registry to useDynamicField or <DrivenForm validatorRegistry={…} />, then reference the rules by name from any schema.

PropertyTypeNotes
optionsFieldOption[]For selects, radios, multi-selects.

A FieldOption is { id: string | number, label: string } plus any extra keys your component needs — passed through untouched.

PropertyTypeNotes
helpTextstringHelper copy below the input.
placeholderstringPlaceholder text.
isHiddenbooleanValue stays in form data but the field never renders. For values expressions need but users shouldn’t see.

isHidden and show are different: show: false unmounts the field and unregisters its validation; isHidden keeps it fully live and merely invisible.

PropertyTypeNotes
fieldsFieldSchema[]Presence makes this node a container.

Register container types with registry.registerContainer(type, Component). A container with no registered component renders its children bare — handy for grouping fields under one show expression without adding markup.

PropertyTypeNotes
columnFieldsFieldSchema[]The row template. Full field schemas — validation, expressions and nesting all work per row.
minRowsnumberRemove is disabled at this count. Default 0.
maxRowsnumberAdd is disabled at this count. Default unlimited.
initialRowsnumberRows to seed when value is absent. Falls back to minRows.

Seeding order is valueinitialRowsminRows → none. The value is an array of row objects, and that’s exactly what onSave receives. See repeaters & tables.

FieldSchema has an index signature: any key you add is passed through to your component untouched as field.yourKey. Icons, column widths, analytics ids, a currency symbol — the engine neither validates nor interprets them.

That’s the escape hatch that keeps the schema from needing to grow a property every time a design does.

{
"id": "onboarding",
"fields": [
{
"name": "accountType",
"type": "radio",
"label": "Account type",
"order": 1,
"options": [
{ "id": "individual", "label": "Individual" },
{ "id": "business", "label": "Business" }
],
"validation": [{ "name": "required", "message": "Pick one" }]
},
{
"name": "businessDetails",
"type": "section",
"label": "Business details",
"order": 2,
"show": "%accountType% == 'business'",
"fields": [
{
"name": "companyName",
"type": "text",
"label": "Registered name",
"order": 1,
"required": true,
"validation": [{ "name": "required", "message": "Required" }]
},
{
"name": "employees",
"type": "number",
"label": "Employees",
"order": 2,
"helpText": "Approximate is fine.",
"validation": [{ "name": "min", "value": 1, "message": "Must be at least 1" }]
}
]
}
]
}

Open in playground