Skip to content

Form model

form.json (inside the .dmf) is the canonical form definition. It separates fields (the data model) from layout (how they’re arranged). A minimal renderer only needs fields; a full one walks steps.

Top-level

{
"id": "contact_form",
"name": "Contact form",
"description": "Say hello",
"schemaVersion": 1,
"submitPolicy": "Anonymous",
"fields": [ /* FieldDefinition[] */ ],
"steps": [ /* Step[] — layout */ ],
"style": { /* FormStyle */ },
"cardLayout": { /* optional single-record card view */ },
"lockedStyleFields": [ /* style paths the author pinned */ ],
"messages": { "<slotId>": "custom message" }
}
FieldTypeNotes
idstringStable form id (snake_case).
namestringDisplay name.
descriptionstring?Optional.
schemaVersionintPer-form author-managed counter, starts at 1. Copied into the submission formVersion. Not a format version.
submitPolicystring"Anonymous" or "Authenticated".
fieldsarrayThe data model — see field kinds.
stepsarrayLayout tree (below).
styleobject?Form-level theme/style (see palette/elementCss in the bundle).
cardLayoutobject?Optional layout for the single-record card / page-through view. Renderers that only render the form for submission can ignore it.
lockedStyleFieldsstring[]Style paths the author explicitly pinned (authoring hint; not needed to render).
messagesobject?Form-level message-slot overrides (e.g. a validation banner).

FieldDefinition

Each entry in fields[]:

{
"id": "f1",
"name": "email",
"label": "Email",
"description": null,
"placeholder": null,
"autocomplete": "email",
"kind": "email",
"required": true,
"defaultValue": null,
"calculatedExpression": null,
"visibleWhen": null,
"isPrimaryDisplay": false,
"indexed": false,
"validation": [ /* ValidationRule[] */ ],
"messages": { "<checkId>": "custom error" },
"style": { /* per-field Style override (optional) */ },
"labelStyle": { /* per-field label Style override (optional) */ },
"choice": { "allowCustom": false, "choices": [ { "value": "a", "label": "A" } ], "display": "Radios", "columns": 2 },
"number": { "min": 0, "max": 100, "decimalPlaces": 0, "format": "N0" },
"money": { "currency": "EUR", "decimalPlaces": 2 },
"text": { "minLength": 0, "maxLength": 200, "pattern": null },
"scale": { "min": 1, "max": 5, "minLabel": "Low", "maxLabel": "High", "shape": "Circle" },
"date": { "min": "2020-01-01", "max": "2030-12-31" },
"attachment": { "acceptedExtensions": [".pdf"], "maxSizeBytes": 5000000 },
"relation": { "targetFormId": "...", "displayFieldId": "...", "multiple": false }
}
  • name is the storage key used in submission values and expressions.
  • autocomplete is an optional HTML autocomplete token (e.g. email, name).
  • kind selects the editor + value shape — see field kinds.
  • Exactly one kind-specific option block (choice/number/money/scale/…) is populated, matching kind.
  • calculatedExpression / visibleWhen are DSL strings — see expressions.
  • isPrimaryDisplay marks the record’s title field; indexed requests a DB index (receiver-side hint).
  • style / labelStyle are optional per-field design overrides (authoring detail; a structure-only renderer can ignore them).

Layout: steps → sections → rows → columns

Form
└─ steps[] (one step = one wizard page; render inline if you don't do wizards)
└─ sections[]
└─ rows[] ( columnsPerRow: int = 12 )
└─ columns[] ( polymorphic — discriminated by "kind" )

Each Row has columnsPerRow (grid track count, default 12). Each Column carries span (tracks, default 12) and an optional stackBelowPx (stack full-width below that viewport width, default 640). Column kinds (discriminated by kind):

Column kindRenders
fielda field, by fieldIdfields[].id
groupa titled, optionally-collapsible container with nested rows[] + its own visibleWhen
richtextMarkdown block (body in markdown)
imagea static image (source, altText, maxHeight, maxWidth, align)
divideran <hr> (thickness, color)
spacervertical space (height)
headinga heading (text, level 1–4)
buttona button — name + label (required), action (None/Submit/Save/Reset/NextStep/PrevStep), variant, iconGlyph

A field referenced by a field column is rendered where the column sits; fields not placed in any column are typically auto-appended (renderer choice).

Styling

Authoring tools bake resolved styles into the .dmf v3:

  • elementCss.json — element-key → CSS string (e.g. field/<id>, form/<id>).
  • palette.css:root light/dark theme variables.
  • fonts.css@font-face for embedded fonts.

A renderer applies these on top of a structural layout layer. Rendering structure-only (ignoring the author’s design) is a valid mode — drop the palette + elementCss and your host’s CSS applies.

See Build your own for a minimal renderer walkthrough.