Reference > Admin > Form Model
Form Model Reference
API reference for the Form Model system — declarative forms for the Webiny admin UI.
- How to create forms using the
FormModelAPI? - What field types are available and how to configure them?
- How to arrange fields with the layout builder?
- How to add validation, conditional visibility, and computed fields?
- What built-in renderers are available?
- How to create custom field renderers?
See Page Settings for end-to-end usage examples.
Overview
The Form Model is Webiny’s declarative form system for the admin UI. It provides a fluent builder API for defining fields, arranging them with a layout builder, validating with Zod schemas or imperative rules, and rendering with React. Fields support conditional visibility, computed values, and deeply nested object structures with dynamic zones.
The typical workflow is: define fields via form.fields(), arrange them with form.setLayout(), and render with the <FormView> component. For extending existing forms (e.g., page settings), use form.fields() and form.layout() to add new fields and layout nodes to an existing form model.
FormModel
field
Gets a single field instance by name. Supports dot-notation paths for nested object fields.
Signature:
form.field(name: string): IFieldParameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Field name, or dot-notation path (e.g. "address.city") |
getField
Gets a field instance by name, returning undefined if the field does not exist.
Signature:
form.getField(name: string): IField | undefinedParameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Field name |
fields
Defines or adds fields to the form model. Can be called multiple times to extend an existing form.
Signature:
form.fields(factory: (fields: IFieldBuilderRegistry) => Record<string, IFieldBuilder | undefined>): voidParameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
factory | function | Yes | Callback receiving the field builder registry, returns a record of named field builders |
removeField
Removes a field from the form model.
Signature:
form.removeField(name: string): voidParameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Name of the field to remove |
layout
Modifies the existing layout by adding, removing, or repositioning layout nodes.
Signature:
form.layout(factory: (layout: ILayoutModifier) => (LayoutNode | IPositionedLayoutNode)[]): voidParameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
factory | function | Yes | Callback receiving the layout modifier, returns an array of layout nodes |
Overload — access a layout node by ID:
form.layout(nodeId: string): ILayoutNodeAccessHandle| Parameter | Type | Required | Description |
|---|---|---|---|
nodeId | string | Yes | ID of the layout node to access |
setLayout
Sets the initial form layout. Use this when defining a form from scratch.
Signature:
form.setLayout(factory: (layout: ILayoutBuilder) => ILayoutNodeBuilder[]): voidParameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
factory | function | Yes | Callback receiving the layout builder, returns an array of builders |
getData
Returns all form data as a plain object.
Signature:
form.getData(): Record<string, unknown>This method takes no parameters.
setData
Sets the form data, populating all field values.
Signature:
form.setData(data: Record<string, unknown>): voidParameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
data | Record<string, unknown> | Yes | The form data object |
validate
Validates all fields and form-level rules. Returns true if the form is valid.
Signature:
form.validate(): Promise<boolean>This method takes no parameters.
submit
Validates the form and returns the form data if valid, or false if validation fails.
Signature:
form.submit<T = Record<string, unknown>>(): Promise<T | false>This method takes no parameters.
addRule
Adds a cross-field validation rule. Accepts a Zod schema or an imperative validation function.
Signature:
form.addRule(rule: FormRule): voidParameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
rule | FormRule | Yes | A z.ZodTypeAny schema or a (form) => IFormError[] function |
reset
Resets the form to its initial state, clearing all dirty flags and validation results.
Signature:
form.reset(): voidThis method takes no parameters.
focusField
Focuses a specific field, automatically activating any tabs needed to make it visible.
Signature:
form.focusField(name: string): voidParameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Name of the field to focus |
Properties
| Property | Type | Description |
|---|---|---|
isDirty | boolean | Whether any field value has changed |
isValid | boolean \| null | Validation result, null if not yet validated |
submitted | boolean | Whether the form has been submitted |
errors | IFormError[] | Array of { path, label?, message } objects |
vm | IFormVM | View model for rendering |
Field Types
All fields are created via the fields registry callback passed to form.fields(). Each factory method returns a chainable field builder.
text
Creates a text field. Value: string | null. Default renderer: textInput.
Signature:
fields.text(): TextFieldBuildernumber
Creates a number field. Value: number | null. Default renderer: numberInput. Automatically normalizes string input to numbers.
Signature:
fields.number(): NumberFieldBuilderboolean
Creates a boolean field. Value: boolean | null. Default renderer: switch.
Signature:
fields.boolean(): BooleanFieldBuilderdatetime
Creates a date/time field. Default renderer: dateTimeInput. Call a variant method to set the subtype:
| Variant | Value Format | Description |
|---|---|---|
.dateOnly() | "2026-05-01" | Calendar date only |
.timeOnly() | "14:30:00" | Time only |
.withTimezone() | "2026-05-01T14:30:00+02:00" | Date+time with offset |
.withoutTimezone() | "2026-05-01T14:30:00.000Z" | Date+time in UTC |
.monthOnly() | "2026-05" | Month picker |
.weekOnly(options?) | "2026-W18" | Week picker |
.yearOnly(options?) | 2026 | Year picker |
.dateRange() | { from, to } | Date range picker |
.multipleDates() | string[] | Multiple dates |
.multipleMonths() | string[] | Multiple months |
.multipleYears(options?) | number[] | Multiple years |
Additional chainable methods:
| Method | Description |
|---|---|
.presets([...]) | Quick-select preset buttons with label and value function |
.displayFormat(format) | Custom display format using date-fns tokens |
Signature:
fields.datetime(): DateTimeFieldBuilderfile
Creates a file field. Value: FileValue | null (object with id, name, size, mimeType, src, width, height). Default renderer: filePicker.
Signature:
fields.file(): FileFieldBuilderfileUrl
Creates a file URL field. Value: string | null (URL only). Default renderer: fileUrlPicker.
Signature:
fields.fileUrl(): FileUrlFieldBuilderobject
Creates an object field for nested structures, lists, and dynamic zones. Value: Record<string, unknown> | null. Default renderer: objectAccordionSingle.
Signature:
fields.object(): ObjectFieldBuilderSee Object Fields for the full object-specific API.
Field Builder Methods
These chainable methods are available on all field builders returned by the field type factories.
label
Sets the field label.
Signature:
builder.label(text: string): thisdescription
Sets the description text displayed below the field.
Signature:
builder.description(text: string): thishelp
Sets the help text.
Signature:
builder.help(text: string): thisnote
Sets a supplementary note.
Signature:
builder.note(text: string): thisplaceholder
Sets the input placeholder text.
Signature:
builder.placeholder(text: string): thisdefaultValue
Sets the default value. Can be a static value or a function for dynamic defaults.
Signature:
builder.defaultValue(value: unknown): thisrequired
Marks the field as required.
Signature:
builder.required(message?: string): thisParameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
message | string | No | Custom validation error message |
requiredWhen
Conditionally marks the field as required based on other field values.
Signature:
builder.requiredWhen(fn: (form: IFormModel) => boolean, message?: string): thisParameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
fn | function | Yes | Predicate receiving the form model |
message | string | No | Custom validation error message |
schema
Sets a Zod validation schema for the field.
Signature:
builder.schema(zodSchema: z.ZodTypeAny): thisParameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
zodSchema | z.ZodTypeAny | Yes | Zod schema object |
renderer
Overrides the default renderer for the field.
Signature:
builder.renderer(name: string, settings?: Record<string, unknown>): thisParameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Renderer name |
settings | Record<string, unknown> | No | Renderer-specific settings |
options
Adds value options. Automatically switches text/number fields to the dropdown renderer.
Signature:
builder.options(opts: IValueOption[] | ((form: IFormModel) => IValueOption[])): thisParameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
opts | IValueOption[] \| function | Yes | Static array or function returning options dynamically |
Each IValueOption has: { label: string, value: string | number, disabled?: boolean }.
list
Converts the field to an array field. Automatically switches renderers (e.g., textInput to textInputs, objectAccordionSingle to objectAccordionMultiple).
Signature:
builder.list(): thishidden
Hides the field from the UI. The value remains in the form data.
Signature:
builder.hidden(): thisdisabled
Disables the field.
Signature:
builder.disabled(value?: boolean): thisrules
Sets conditional visibility and disable rules for the field. See Conditional Rules.
Signature:
builder.rules(rules: IRule[]): thiscomputed
Makes the field always computed — its value is recalculated whenever dependencies change.
Signature:
builder.computed(fn: (form: IFormModel) => unknown): thiscomputedUntilDirty
Makes the field computed until the user manually edits it.
Signature:
builder.computedUntilDirty(fn: (form: IFormModel) => unknown): thisbeforeChange
Adds a transform that runs before a value change is applied. Return the transformed value.
Signature:
builder.beforeChange(fn: (value: unknown, form: IFormModel) => unknown): thisafterChange
Adds a callback that runs after a value change is applied.
Signature:
builder.afterChange(fn: (value: unknown, form: IFormModel) => void): thisafterSetValue
Adds a callback that runs after a programmatic setValue() call.
Signature:
builder.afterSetValue(fn: (value: unknown, form: IFormModel) => void): thisonBlur
Adds a callback that runs when the field loses focus.
Signature:
builder.onBlur(fn: (value: unknown, form: IFormModel) => void): thiscloneValue
Sets custom clone logic for when a list item containing this field is duplicated.
Signature:
builder.cloneValue(fn: (value: unknown) => unknown): thistags
Tags the field for programmatic lookup.
Signature:
builder.tags(tags: string[]): thisField Instance
The IField interface represents a runtime field instance, returned by form.field("name").
getValue
Gets the current field value.
Signature:
field.getValue<T = unknown>(): TsetValue
Sets the field value, triggering beforeChange and afterChange callbacks.
Signature:
field.setValue(value: unknown): voidsetValueSilent
Sets the field value without triggering callbacks.
Signature:
field.setValueSilent(value: unknown): voidas
Casts the field to a typed field interface.
Signature:
field.as<T extends keyof FieldTypeMap>(type: T): FieldTypeMap[T]Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
type | string | Yes | Field type to cast to (e.g. "object", "text") |
validate
Validates the field against its schema, required status, and rules.
Signature:
field.validate(options?: { force?: boolean }): Promise<boolean>blur
Triggers the blur event on the field.
Signature:
field.blur(): voidfocus
Requests focus on the field.
Signature:
field.focus(): voidremove
Removes the field from the form model.
Signature:
field.remove(): voidsetDisabled
Sets the disabled state of the field.
Signature:
field.setDisabled(value: boolean): voidsetVisible
Sets the visibility of the field.
Signature:
field.setVisible(value: boolean): voidProperties
| Property | Type | Description |
|---|---|---|
name | string | Field name |
type | string | Field type (e.g. "text", "number") |
visible | boolean | Whether the field is visible |
disabled | boolean | Whether the field is disabled |
qualifiedName | string | Full dot-notation path for nested fields |
isComputed | boolean | Whether the field is computed |
vm | IFieldVM | View model for rendering |
Object Fields
Object fields support nested structures, lists, and dynamic zones (templates). Access via form.field("name").as("object") or use the ObjectFieldBuilder.
Builder Methods
fields
Defines child fields for the object.
Signature:
builder.fields(fn: (fields: IFieldBuilderRegistry) => Record<string, IFieldBuilder>): thistemplate
Defines a dynamic zone template. Calling .template() automatically switches the renderer to dynamicZone.
Signature:
builder.template(id: string, configure: (t: ITemplateBuilder) => void): thisThe ITemplateBuilder supports:
| Method | Description |
|---|---|
.label(text) | Template display name |
.icon(icon) | Template icon |
.fields(fn) | Define template fields |
.visible(predicate) | Conditional template visibility |
listSchema
Adds a Zod validation schema for the list as a whole.
Signature:
builder.listSchema(schema: z.ZodTypeAny): thisRuntime Methods
These methods are available on IObjectField, accessed via form.field("name").as("object").
addItem
Adds a new item to a list object field. For templated fields, pass a template ID.
Signature:
objectField.addItem(templateIdOrData?: string | Record<string, unknown>, data?: Record<string, unknown>): voidremoveItem
Removes an item from a list object field by index.
Signature:
objectField.removeItem(index: number): voidmoveItem
Moves an item within a list object field.
Signature:
objectField.moveItem(fromIndex: number, toIndex: number): voidduplicateItem
Duplicates an item in a list object field.
Signature:
objectField.duplicateItem(index: number): voidsetTemplate
Sets the active template on a single-value templated object field.
Signature:
objectField.setTemplate(templateId: string): voidRuntime Template Management
Templates can be added or removed at runtime via the templates property:
const sections = form.field("sections").as("object");
sections.templates.remove("text");
sections.templates.add("banner", t => {
t.label("Banner").fields(f => ({
headline: f.text().label("Headline").required()
}));
});Properties
| Property | Type | Description |
|---|---|---|
isList | boolean | Whether this is a list object field |
isTemplated | boolean | Whether templates are defined |
activeTemplateId | string \| null | Currently selected template ID |
availableTemplates | ITemplateVM[] | Available templates |
children | Map<string, IField> | Child fields (for single objects) |
items | IListItemField[] | List items (for list objects) |
Layout Builder
The layout builder controls how fields are arranged in the UI. Use form.setLayout() for initial layout definition, or form.layout() to modify an existing layout.
row
Arranges one or more fields in a horizontal row.
Signature:
layout.row(...fieldIds: string[]): IRowBuilderseparator
Adds a visual divider between layout sections.
Signature:
layout.separator(): ISeparatorBuildertabs
Creates a tabbed layout container.
Signature:
layout.tabs(id?: string): ITabsBuilderThe ITabsBuilder supports chaining:
| Method | Description |
|---|---|
.renderer(name) | Set tab renderer (e.g. "tabsVertical") |
.tab(id, configure) | Add a tab |
.rules(rules) | Conditional visibility rules for the tab group |
The tab configure callback receives an ITabBuilder:
| Method | Description |
|---|---|
.label(text) | Tab label |
.description(text) | Tab description |
.icon(icon) | Tab icon |
.layout(fn) | Tab content layout |
.rules(rules) | Tab-level visibility rules |
element
Adds a custom rendered element to the layout.
Signature:
layout.element(renderer: string, props?: Record<string, unknown>): IElementBuilderobject
Defines the inner layout for an object field.
Signature:
layout.object(fieldName: string, layout: (l: ILayoutBuilder) => ILayoutNodeBuilder[]): IObjectBuilderOverload — per-template layouts for dynamic zones:
layout.object(fieldName: string, templateLayouts: Record<string, (l: ILayoutBuilder) => ILayoutNodeBuilder[]>): IObjectBuilderPositioning
When modifying an existing layout, use .after() or .before() to position relative to existing fields:
layout.row("newField").after("existingField");
layout.row("anotherField").before("existingField");The .replace(target) method removes the target and inserts the new node in its place.
Conditional Rules
Rules control field visibility and disabled state based on other field values. Pass them via the .rules() builder method.
Rule structure:
| Property | Type | Description |
|---|---|---|
type | string | Rule type (use "condition") |
target | string | Field name to watch |
operator | string | Comparison operator (see below) |
value | string \| null | Comparison value (null for unary operators) |
action | string | "hide" or "disable" |
Operators
| Operator | Description |
|---|---|
"eq" | Equal to value |
"neq" | Not equal to value |
"isEmpty" | Null, undefined, empty string, or empty array |
"isNotEmpty" | Has a non-empty value |
"isTruthy" | Boolean coercion is true |
"isFalsy" | Boolean coercion is false |
"matches" | Exact string match |
Built-in Renderers
| Renderer | Field Type | Settings | Description |
|---|---|---|---|
textInput | text | — | Single-line text input |
textarea | text | { rows?: number } | Multi-line text area |
textInputs | text (list) | { addItemLabel?: string } | List of text inputs |
textareas | text (list) | { addItemLabel?: string } | List of textareas |
tags | text (list) | — | Comma-separated tag input |
codeEditor | text | { language?: string, height?: number } | Code editor with syntax highlighting |
dropdown | text, number | — | Select dropdown (auto when .options()) |
radioButtons | text, number | — | Radio button group |
checkboxes | text (list), number (list) | — | Checkbox group |
numberInput | number | — | Number input |
numberInputs | number (list) | { addItemLabel?: string } | List of number inputs |
switch | boolean | — | Toggle switch |
dateTimeInput | datetime | { type, displayFormat?, yearRange?, weekStartsOn?, presets? } | Date/time picker |
dateTimeInputs | datetime (list) | { type, displayFormat?, weekStartsOn?, addItemLabel? } | List of date/time pickers |
filePicker | file | — | File picker with full metadata |
fileUrlPicker | fileUrl | — | File picker returning URL only |
objectAccordionSingle | object | { open?: boolean } | Single object in accordion |
objectAccordionMultiple | object (list) | { open?, container?, itemTitle?, addItemLabel? } | List of objects in accordions |
dynamicZone | object (templates) | { container?: boolean } | Template picker zone |
passthrough | object | — | Renders child fields inline |
keyValueTags | object (list) | { addItemLabel?: string } | Key-value tag pairs |
hidden | any | — | Hidden field (no UI rendered) |
Automatic Renderer Switching
- Calling
.options()on text/number fields switches todropdown - Calling
.list()on datetime switches todateTimeInputs - Calling
.list()on object switches toobjectAccordionMultiple - Calling
.template()on object switches todynamicZone
Custom Renderers
createFieldRenderer
Creates a custom field renderer component.
Signature:
createFieldRenderer<TName extends string>(
render: (props: { field: RendererField<TName> }) => React.ReactNode
): React.ComponentType<{ field: IFieldVM }>createObjectFieldRenderer
Creates a custom renderer for object fields.
Signature:
createObjectFieldRenderer<TName extends string>(
render: (props: { field: ObjectRendererField<TName> }) => React.ReactNode
): React.ComponentType<{ field: IFieldVM }>FormView
The FormView component renders a form model in the UI.
Signature:
<FormView form={formModel.vm} renderers={fieldRenderers} layoutRenderers={layoutRenderers} />Props:
| Prop | Type | Required | Description |
|---|---|---|---|
form | IFormVM | Yes | The form view model |
renderers | FieldRenderers | No | Custom field renderer overrides |
layoutRenderers | LayoutRenderers | No | Custom layout renderer overrides |