FormDefinition

The single JSON document that describes a form. Everything in Formora reads it; nothing else is the source of truth.

Top level

KeyTypeNotes
id, namestringRequired, non-empty.
schemaVersioninteger ≥ 1Bump it when the form changes. Submissions record the version they answered.
createdAt, updatedAtISO 8601 stringFor example 2026-01-01T00:00:00.000Z.
fieldsarrayWhat exists. Order here does not control layout.
layoutobjectWhere each field sits on the grid.
themeobjectOptional. Design tokens.
logicobjectOptional. Visibility rules and calculated fields.

fields

A field with every option:

field
{
  "id": "age",
  "type": "number",
  "label": "Age",
  "description": "Must be 18 or over.",
  "required": true,
  "defaultProps": { "placeholder": "18" },
  "validation": { "min": 18, "max": 120 },
  "style": { "borderColor": "#22c55e", "radius": "lg" }
}
KeyNotes
idUnique. Must start with a letter and use only letters, digits, _ or - (up to 64 characters). This becomes the key in the answers.
typeName of a registered field type, for example text. See field types.
labelRequired. Shown above the input.
descriptionOptional help text shown under the input.
requiredDefaults to false. A value counts as missing if it is empty, an unticked checkbox, or an empty list.
defaultPropsSettings for the field type, such as placeholder or options. Set defaultValue to pre-fill the field.
validationminLength, maxLength, pattern (a regular expression string) apply to text answers. min, max apply to numbers.
stylePer-field overrides: textColor, backgroundColor, borderColor, radius, fontSize. Use sparingly; prefer the form-wide theme.

layout

A 12-column grid. Each row lists columns; each column has a span (1 to 12) and the fieldId it shows.

  • A row's spans must not add up to more than 12.
  • Every fieldId must exist in fields.
  • A field missing from the layout is still rendered, full width, after the last row.
  • On narrow screens (640px and below) columns stack.

theme

colors (primary, background, text), radius (none, sm, md, lg, full), font and density (compact, comfortable, spacious). Anything you leave out falls back to the stylesheet default. See theming.

logic

visibility rules hide or show a targetFieldId based on conditions (fieldId, an operator such as equals, notEquals, contains, greaterThan, lessThan, isEmpty, isNotEmpty, and a value), combined with match of all or any. A field referenced by a rule or targeted by it must exist, or the definition is rejected. A hidden field is never required and its answer is dropped before submission — the renderer applies this live, and the server re-applies it independently rather than trusting the client.

calculated entries derive a number field's value from other number fields via a formula (a small, safe arithmetic language: +, −, ×, ÷, parentheses, and field ids as identifiers — no arbitrary code execution, since the formula is authored by the form's creator but runs in the respondent's browser). The target field renders read-only. The server recomputes it from the submitted inputs before saving, overwriting whatever the client actually posted.

Validation errors

A definition is checked before it is used. Duplicate field ids, over-wide rows, and layout or logic that points at a missing field are all rejected with a message naming the problem. The renderer shows that message instead of a broken form.

FormSubmission

The second document, created when someone submits:

submission
{
  "formId": "contact",
  "schemaVersion": 1,
  "answers": { "name": "Ada Lovelace", "email": "ada@example.com" },
  "meta": { "submittedAt": "2026-09-21T10:30:00.000Z" }
}

answers is keyed by field id. meta is optional and can also carry ip and userAgent.