Skip to content

Form-Document Schema

Generated from the committed form-document-schema.json artifact — regenerate with bun run generate:form-document-schema (after a registry change) followed by bun run generate:form-document-schema-docs. Do not hand-edit this page.

The single JSON Schema (draft 2020-12) describing the canonical step_configuration document a FORM step persists: a discriminated union on each element's type, composed from every registered element plugin's config and validation contract. It is generated, never hand-written, so it cannot drift from the plugins that define each element type.

It serves three roles from one artifact: a strict authoring/save validator, this generation-target reference, and — later — the AI structured-output tool schema.

Schema version 5 (draft https://json-schema.org/draft/2020-12/schema). The canonical step_configuration shape for a FORM step (SPIKE §11.1), composed from every registered element plugin's configSchema + validationContract (SPIKE §11.2) -- generated from utils/registry.js, never hand-written.

Envelope

Top-level keys of the step_configuration document.

KeyRequiredDescription
schemaVersionyesFormat version normalize() upgrades every doc to (app/api/utils/form_utils.py CURRENT_SCHEMA_VERSION).
schemayes
elementsMapyesAnswers stay id-keyed permanently (SPIKE §11.1 invariant 2) -- each element's key (above) anchors only the calculated-value/AI-authoring/piping layers. A condition/dependency reference that crosses a LIST/TABLE repeating-scope boundary is rejected by the graph validator (app/api/utils/form_graph.py detect_repeating_scope), not structurally expressible here (SPIKE §11.4.4).
dependenciesnoDerived from every element's conditions.filterSet by canonicalize() -- persisted for the FE client-authority visibility evaluator, but drop-and-recomputed on every normalize pass and never independently authored or trusted (SPIKE §11.1 invariant 6).
settingsnoForm-level overlay resolved by resolveFormSettings against its own defaults (SPIKE §11.1.1) -- an absent/empty object resolves to full defaults.

Element types

One row per registered element plugin -- a discriminated union on elementsMap[id].type. config and validation list the property keys each type accepts (empty means the type carries no config/validation object of its own).

TypeKindConfig keysValidation keys
CHECKBOX_GROUPfielddescription, optionSource, options, defaultrequired
COLUMNSlayout (per-column containment)numberOfColumns
DATEfielddescription, placeholder, default, formatrequired, min, max, compare
DATE_TIMEfielddescription, placeholder, default, format, is24hrrequired, min, max, compare
DIVIDERfieldspacing
DROPDOWNfielddescription, placeholder, multiple, optionSource, options, defaultrequired
FILE_DOWNLOADfielddescription, placeholder, files
FILE_UPLOADfielddescription, placeholder, extensionsrequired
GROUPcontainer (flat containment)
HEADINGfieldtext, size, align
IMAGEfieldurl, width, height, alt, align
LABELfielddescription
LINKfieldurl, text, align
LISTcontainer (flat containment)description, instanceLabel, initialInstances, minInstances, maxInstances, canAdd, canRemoverequired
NUMBER_INPUTfielddescription, placeholder, default, maskPreset, mask, sensitiverequired, min, max, integer, compare
PARAGRAPHfieldtext
PHONEfielddescription, placeholder, defaultCountryrequired
RADIO_GROUPfielddescription, optionSource, options, defaultrequired
RICH_TEXTfielddescription, default, linesrequired
SIGNATUREfielddescription, enabledModes, defaultDrawThickness, signatureFonts, allowedFileTypesrequired
TABLEtable (per-row column schema)description, initialRows, canAddRows, canRemoveRows, columnsminRows, maxRows, required
TEXT_AREAfielddescription, placeholder, default, sensitiverequired, minLength, maxLength, format, regex, regexMessage
TEXT_INPUTfielddescription, placeholder, default, maskPreset, mask, sensitiverequired, minLength, maxLength, format, regex, regexMessage

Constraints not expressible in JSON Schema

Every structural/type/shape constraint is enforced by the schema itself. The following are documented as notes because they depend on runtime evaluation (JSONLogic) or cross-element graph analysis the schema cannot express:

  • Answers stay id-keyed permanently (SPIKE §11.1 invariant 2) -- each element's key (above) anchors only the calculated-value/AI-authoring/piping layers. A condition/dependency reference that crosses a LIST/TABLE repeating-scope boundary is rejected by the graph validator (app/api/utils/form_graph.py detect_repeating_scope), not structurally expressible here (SPIKE §11.4.4).
  • Author-stable semantic anchor for the calculated-value/AI-authoring/piping layers only -- answers stay id-keyed permanently (SPIKE §11.1 invariant 2).
  • Derived back-pointer, recomputed by canonicalize() on every normalize pass -- never authored.
  • Derived from every element's conditions.filterSet by canonicalize() -- persisted for the FE client-authority visibility evaluator, but drop-and-recomputed on every normalize pass and never independently authored or trusted (SPIKE §11.1 invariant 6).
  • Form-level overlay resolved by resolveFormSettings against its own defaults (SPIKE §11.1.1) -- an absent/empty object resolves to full defaults.
  • ISO date string, not a JS Date -- coerced through parseToType at evaluation time (engine/validationRules.js valueAtLeast/valueAtMost), never enforced as a date format here.
  • JSONLogic-evaluated predicate (engine/validationRules.js RULE_KINDS.compare) -- shape only, never the comparison semantics, which are compiled + evaluated at runtime.
  • filterSet is compiled to JSONLogic at evaluation time (engine/jsonLogicCompiler.js compileFilterSet) -- described here by shape only, never the compiled/evaluated semantics.

Internal documentation — gated behind Cloudflare Access.