Fields
The fields property defines how each form field is displayed and behaves. Each key must match a property name in your schema.
Field Definition
{
"fields": {
"fieldName": {
"label": "Display Label",
"description": "Help text",
"placeholder": "Placeholder text",
"type": "text",
"visibleWhen": "...",
"requiredWhen": "...",
"enabledWhen": "...",
"validations": [...],
"options": [...]
}
}
}
Common Properties
| Property | Type | Description |
|---|---|---|
label | string | Display label shown above the field |
description | string | Help text shown below the field |
placeholder | string | Placeholder text inside the input |
type | string | Field type override (see Field Types) |
defaultValue | any | Initial value when form loads (must match type) |
readonlyWhen | FEELExpression | When this field is read-only |
variant | string | Presentation variant (see Variants) |
variantConfig | Record<string, unknown> | Variant-specific configuration |
Default Values
Use defaultValue to pre-populate a field when the form loads:
{
"country": {
"label": "Country",
"type": "select",
"defaultValue": "us",
"options": [
{ "value": "us", "label": "United States" },
{ "value": "ca", "label": "Canada" }
]
},
"quantity": {
"label": "Quantity",
"type": "integer",
"defaultValue": 1
},
"subscribe": {
"label": "Subscribe to newsletter",
"type": "boolean",
"defaultValue": true
}
}
Resolution order (highest priority wins):
initialDataprop passed at runtimedefaultValuefrom the field definition- Implicit type defaults (booleans default to
false)
Notes:
defaultValuemust match the field's schema type (string for text/select, number for number/integer, boolean for boolean)- Display fields cannot have
defaultValue - For array item fields,
defaultValueis applied each time a new item is added
Field Types
The type property determines how the field is rendered. If omitted, it's inferred from the schema.
Text Input Types
| Type | Description | Schema Type |
|---|---|---|
text | Single-line text input | string |
textarea | Multi-line text input | string |
email | Email input with validation | string (format: email) |
url | URL input | string (format: uri) |
password | Password input (masked) | string |
phone | Phone number input | string |
{
"bio": {
"label": "Biography",
"type": "textarea",
"placeholder": "Tell us about yourself..."
}
}
Phone Fields
Use type: "phone" for phone number inputs. This renders an HTML tel input, which triggers the phone keyboard on mobile devices.
Basic phone — simple phone collection without a country picker:
{
"phone": {
"type": "phone",
"label": "Phone Number",
"placeholder": "(555) 555-5555"
}
}
International phone — with country code picker, flag icons, and auto-formatting. Uses the phone-international variant and stores values in E.164 format (+12015550123):
{
"phone": {
"type": "phone",
"label": "Phone Number",
"variant": "phone-international",
"variantConfig": {
"defaultCountry": "us"
}
}
}
The international variant requires format: "phone" in the schema to enforce E.164 validation:
{
"schema": {
"properties": {
"phone": { "type": "string", "format": "phone" }
}
}
}
| Scenario | Variant | Schema Format |
|---|---|---|
| Simple phone collection | (none) | string (no format) |
| International with country picker | phone-international | string with format: "phone" |
| Phone with specific regex | (none) | string + FEEL matches() validation |
Only add format: "phone" when using the phone-international variant. For basic phone fields, use plain type: "string" — the format constraint enforces strict E.164, which users won't type manually without a country picker.
Numeric Types
| Type | Description | Schema Type |
|---|---|---|
number | Decimal number input | number |
integer | Whole number input | integer |
{
"quantity": {
"label": "Quantity",
"type": "integer",
"description": "Enter a whole number"
}
}
Boolean Type
| Type | Description | Schema Type |
|---|---|---|
boolean | Checkbox or toggle | boolean |
{
"subscribe": {
"label": "Subscribe to newsletter",
"type": "boolean"
}
}
Date Types
| Type | Description | Schema Type |
|---|---|---|
date | Date picker | string (format: date) |
datetime | Date and time picker | string (format: date-time) |
{
"appointmentDate": {
"label": "Preferred Date",
"type": "date"
}
}
Selection Types
| Type | Description | Schema Type |
|---|---|---|
select | Dropdown selection | string with enum |
multiselect | Multiple selection | array of strings |
{
"country": {
"label": "Country",
"type": "select",
"options": [
{ "value": "us", "label": "United States" },
{ "value": "ca", "label": "Canada" },
{ "value": "uk", "label": "United Kingdom" }
]
}
}
Matrix Type
| Type | Description | Schema Type |
|---|---|---|
matrix | Grid of rows × columns (e.g. Likert scale) | object |
{
"service_rating": {
"label": "Rate our service",
"type": "matrix",
"rows": [
{ "id": "speed", "label": "Speed" },
{ "id": "quality", "label": "Quality" },
{ "id": "support", "label": "Support" }
],
"columns": [
{ "value": 1, "label": "Poor" },
{ "value": 2, "label": "Fair" },
{ "value": 3, "label": "Good" },
{ "value": 4, "label": "Excellent" }
]
}
}
For full details, see Matrix Fields below.
Complex Types
| Type | Description | Schema Type |
|---|---|---|
array | Repeatable group of fields | array |
object | Nested field group | object |
computed | Read-only calculated value | N/A |
display | Read-only display content | N/A |
Display Fields
Display fields show read-only content such as headings, metrics, or alerts. They have no user input and are not included in form data.
{
"header": {
"type": "display",
"label": "Section Header",
"content": "Please review the following information."
},
"totalDisplay": {
"type": "display",
"label": "Order Total",
"source": "total",
"format": "currency"
}
}
| Property | Type | Description |
|---|---|---|
content | string | undefined | Static text content |
source | string | undefined | Computed field name to display dynamically |
format | string | undefined | Display format hint (e.g., "currency") |
For rendering details, see Display Fields in the forma-react docs.
Options
For select and multiselect fields, define available choices:
{
"options": [
{ "value": "option1", "label": "Option One" },
{ "value": "option2", "label": "Option Two" }
]
}
Option Properties
| Property | Type | Description |
|---|---|---|
value | string | number | Value stored when selected |
label | string | Display text shown to user |
visibleWhen | string | FEEL expression to conditionally show option |
Conditional Options
Show options based on other field values:
{
"plan": {
"label": "Plan",
"type": "select",
"options": [
{ "value": "free", "label": "Free" },
{ "value": "pro", "label": "Pro" },
{
"value": "enterprise",
"label": "Enterprise",
"visibleWhen": "companySize > 100"
}
]
}
}
Array Fields
For repeatable groups of fields, use type: "array" with itemFields:
{
"schema": {
"properties": {
"contacts": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": { "type": "string" },
"email": { "type": "string" }
}
}
}
}
},
"fields": {
"contacts": {
"label": "Contacts",
"type": "array",
"minItems": 1,
"maxItems": 5,
"itemFields": {
"name": { "label": "Name" },
"email": { "label": "Email" }
}
}
}
}
Array Properties
| Property | Type | Description |
|---|---|---|
itemFields | object | Field definitions for each array item |
minItems | integer | Minimum required items |
maxItems | integer | Maximum allowed items |
Object Fields
For nested data structures, use type: "object". Object fields group nested properties defined in the schema — unlike array fields, they do not use itemFields:
{
"schema": {
"properties": {
"address": {
"type": "object",
"properties": {
"street": { "type": "string" },
"city": { "type": "string" },
"zipCode": { "type": "string" }
}
}
}
},
"fields": {
"address": {
"label": "Address",
"type": "object"
},
"address.street": { "label": "Street" },
"address.city": { "label": "City" },
"address.zipCode": { "label": "ZIP Code" }
},
"fieldOrder": ["address"]
}
Access nested values in expressions with dot notation: address.city, address.zipCode.
Matrix Fields
Matrix fields render a grid where users select one (or more) options per row. Common uses include Likert scales, satisfaction surveys, and rating grids.
Field Definition
{
"service_rating": {
"label": "Rate our service",
"type": "matrix",
"rows": [
{ "id": "speed", "label": "Speed of service" },
{ "id": "quality", "label": "Quality of work" },
{ "id": "support", "label": "Customer support" }
],
"columns": [
{ "value": 1, "label": "Poor" },
{ "value": 2, "label": "Fair" },
{ "value": 3, "label": "Good" },
{ "value": 4, "label": "Excellent" }
]
}
}
Properties
| Property | Type | Required | Description |
|---|---|---|---|
rows | MatrixRow[] | Yes | Row definitions (items/statements to rate) |
columns | MatrixColumn[] | Yes | Column definitions (shared response options) |
multiSelect | boolean | No | Allow multiple selections per row (default: false) |
MatrixRow:
| Property | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Unique identifier (snake_case, used in data keys and FEEL) |
label | string | Yes | Display label |
visibleWhen | string | No | FEEL expression to conditionally show the row |
MatrixColumn:
| Property | Type | Required | Description |
|---|---|---|---|
value | string | number | Yes | Value stored when selected |
label | string | Yes | Display label |
Data Shape
Single-select (default): each row stores one value.
{ "speed": 4, "quality": 3, "support": 4 }
Multi-select (multiSelect: true): each row stores an array of values.
{ "speed": [3, 4], "quality": [4], "support": [2, 3] }
Schema
The corresponding JSON Schema uses type: "object" with one property per row. Each property has an enum matching the column values:
{
"schema": {
"properties": {
"service_rating": {
"type": "object",
"properties": {
"speed": { "type": "integer", "enum": [1, 2, 3, 4] },
"quality": { "type": "integer", "enum": [1, 2, 3, 4] },
"support": { "type": "integer", "enum": [1, 2, 3, 4] }
},
"required": ["speed", "quality", "support"]
}
}
}
}
For multi-select matrices, each row property is an array of the enum type instead.
Categorical Columns
Use string values for non-numeric scales:
{
"columns": [
{ "value": "strongly_disagree", "label": "Strongly Disagree" },
{ "value": "disagree", "label": "Disagree" },
{ "value": "neutral", "label": "Neutral" },
{ "value": "agree", "label": "Agree" },
{ "value": "strongly_agree", "label": "Strongly Agree" }
]
}
Do not use string representations of numbers (e.g. "4") as column values. FEEL functions like mean() return null on strings. Use actual numbers (4) for numeric scales.
FEEL Access
Access matrix values using dot notation — fieldId.rowId:
{
"visibleWhen": "service_rating.speed >= 3",
"rule": "service_rating.quality >= 2",
"expression": "mean([service_rating.speed, service_rating.quality, service_rating.support])"
}
Conditional Rows
Show or hide individual rows based on form state:
{
"rows": [
{ "id": "speed", "label": "Speed" },
{ "id": "quality", "label": "Quality" },
{
"id": "support",
"label": "Customer Support",
"visibleWhen": "used_support = true"
}
]
}
Field Order
The fieldOrder array determines the display order:
{
"fieldOrder": ["firstName", "lastName", "email", "phone"]
}
Fields not in fieldOrder are not displayed.
Presentation Variants
The variant property lets a single field type render as different UI components. For example, a number field can appear as a slider, a stepper, or an NPS scale without changing the field type.
Available Variants
| Field Type | Variant | Description | variantConfig Keys |
|---|---|---|---|
number | slider | Range slider | showValue (boolean) |
number | stepper | Increment/decrement buttons | — |
number | nps | 0–10 NPS scale | lowLabel, highLabel |
number | rating-stars | Star rating | maxStars (default 5) |
number | rating-numeric | Numeric rating buttons | maxRating (default 5) |
phone | phone-international | Country picker with E.164 | defaultCountry (ISO alpha-2, default "us") |
select | radio | Radio button group | — |
select | radio-cards | Card-style radio options | — |
select | button-group | Segmented button group | — |
display | heading | Section heading | level (2–5) |
display | divider | Visual separator | — |
display | metric | Large numeric display | unit |
display | alert | Alert with severity | severity ("info", "warning", "error", "success") |
display | callout | Informational callout | icon (e.g., "info", "lightbulb") |
display | summary | Card-based summary | — |
matrix | likert | Likert scale layout | — |
matrix | dropdown-grid | Dropdowns per row | — |
Example
{
"fields": {
"satisfaction": {
"type": "number",
"label": "How likely are you to recommend us?",
"variant": "nps",
"variantConfig": {
"lowLabel": "Not at all likely",
"highLabel": "Extremely likely"
}
}
}
}
For implementation details on rendering variants in React, see Presentation Variants in the forma-react docs.
Type Inference
In the TypeScript type system, type is a required property on FieldDefinition. At runtime, if type is omitted, it can be inferred from the schema:
| Schema | Inferred Type |
|---|---|
{ "type": "string" } | text |
{ "type": "string", "format": "email" } | email |
{ "type": "string", "format": "phone" } | phone |
{ "type": "string", "format": "date" } | date |
{ "type": "string", "enum": [...] } | select |
{ "type": "integer" } | integer |
{ "type": "number" } | number |
{ "type": "boolean" } | boolean |
{ "type": "array" } | array |
{ "type": "object" } (with matrix field def) | matrix |