FormDefinition
The single JSON document that describes a form. Everything in Formora reads it; nothing else is the source of truth.
Top level
| Key | Type | Notes |
|---|---|---|
id, name | string | Required, non-empty. |
schemaVersion | integer ≥ 1 | Bump it when the form changes. Submissions record the version they answered. |
createdAt, updatedAt | ISO 8601 string | For example 2026-01-01T00:00:00.000Z. |
fields | array | What exists. Order here does not control layout. |
layout | object | Where each field sits on the grid. |
theme | object | Optional. Design tokens. |
logic | object | Optional. Visibility rules and calculated fields. |
fields
A field with every option:
{
"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" }
}| Key | Notes |
|---|---|
id | Unique. Must start with a letter and use only letters, digits, _ or - (up to 64 characters). This becomes the key in the answers. |
type | Name of a registered field type, for example text. See field types. |
label | Required. Shown above the input. |
description | Optional help text shown under the input. |
required | Defaults to false. A value counts as missing if it is empty, an unticked checkbox, or an empty list. |
defaultProps | Settings for the field type, such as placeholder or options. Set defaultValue to pre-fill the field. |
validation | minLength, maxLength, pattern (a regular expression string) apply to text answers. min, max apply to numbers. |
style | Per-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
fieldIdmust exist infields. - 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:
{
"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.