WHAT YOU'LL LEARN
  • How to create forms using the FormModel API?
  • 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?
USAGE EXAMPLES

See Page Settings for end-to-end usage examples.

Overview
anchor

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
anchor

field
anchor

Gets a single field instance by name. Supports dot-notation paths for nested object fields.

Signature:

form.field(name: string): IField

Parameters:

ParameterTypeRequiredDescription
namestringYesField name, or dot-notation path (e.g. "address.city")

getField
anchor

Gets a field instance by name, returning undefined if the field does not exist.

Signature:

form.getField(name: string): IField | undefined

Parameters:

ParameterTypeRequiredDescription
namestringYesField name

fields
anchor

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>): void

Parameters:

ParameterTypeRequiredDescription
factoryfunctionYesCallback receiving the field builder registry, returns a record of named field builders

removeField
anchor

Removes a field from the form model.

Signature:

form.removeField(name: string): void

Parameters:

ParameterTypeRequiredDescription
namestringYesName of the field to remove

layout
anchor

Modifies the existing layout by adding, removing, or repositioning layout nodes.

Signature:

form.layout(factory: (layout: ILayoutModifier) => (LayoutNode | IPositionedLayoutNode)[]): void

Parameters:

ParameterTypeRequiredDescription
factoryfunctionYesCallback receiving the layout modifier, returns an array of layout nodes

Overload — access a layout node by ID:

form.layout(nodeId: string): ILayoutNodeAccessHandle
ParameterTypeRequiredDescription
nodeIdstringYesID of the layout node to access

setLayout
anchor

Sets the initial form layout. Use this when defining a form from scratch.

Signature:

form.setLayout(factory: (layout: ILayoutBuilder) => ILayoutNodeBuilder[]): void

Parameters:

ParameterTypeRequiredDescription
factoryfunctionYesCallback receiving the layout builder, returns an array of builders

getData
anchor

Returns all form data as a plain object.

Signature:

form.getData(): Record<string, unknown>

This method takes no parameters.


setData
anchor

Sets the form data, populating all field values.

Signature:

form.setData(data: Record<string, unknown>): void

Parameters:

ParameterTypeRequiredDescription
dataRecord<string, unknown>YesThe form data object

validate
anchor

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
anchor

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
anchor

Adds a cross-field validation rule. Accepts a Zod schema or an imperative validation function.

Signature:

form.addRule(rule: FormRule): void

Parameters:

ParameterTypeRequiredDescription
ruleFormRuleYesA z.ZodTypeAny schema or a (form) => IFormError[] function

reset
anchor

Resets the form to its initial state, clearing all dirty flags and validation results.

Signature:

form.reset(): void

This method takes no parameters.


focusField
anchor

Focuses a specific field, automatically activating any tabs needed to make it visible.

Signature:

form.focusField(name: string): void

Parameters:

ParameterTypeRequiredDescription
namestringYesName of the field to focus

Properties
anchor

PropertyTypeDescription
isDirtybooleanWhether any field value has changed
isValidboolean \| nullValidation result, null if not yet validated
submittedbooleanWhether the form has been submitted
errorsIFormError[]Array of { path, label?, message } objects
vmIFormVMView model for rendering

Field Types
anchor

All fields are created via the fields registry callback passed to form.fields(). Each factory method returns a chainable field builder.

text
anchor

Creates a text field. Value: string | null. Default renderer: textInput.

Signature:

fields.text(): TextFieldBuilder

number
anchor

Creates a number field. Value: number | null. Default renderer: numberInput. Automatically normalizes string input to numbers.

Signature:

fields.number(): NumberFieldBuilder

boolean
anchor

Creates a boolean field. Value: boolean | null. Default renderer: switch.

Signature:

fields.boolean(): BooleanFieldBuilder

datetime
anchor

Creates a date/time field. Default renderer: dateTimeInput. Call a variant method to set the subtype:

VariantValue FormatDescription
.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?)2026Year picker
.dateRange(){ from, to }Date range picker
.multipleDates()string[]Multiple dates
.multipleMonths()string[]Multiple months
.multipleYears(options?)number[]Multiple years

Additional chainable methods:

MethodDescription
.presets([...])Quick-select preset buttons with label and value function
.displayFormat(format)Custom display format using date-fns tokens

Signature:

fields.datetime(): DateTimeFieldBuilder

file
anchor

Creates a file field. Value: FileValue | null (object with id, name, size, mimeType, src, width, height). Default renderer: filePicker.

Signature:

fields.file(): FileFieldBuilder

fileUrl
anchor

Creates a file URL field. Value: string | null (URL only). Default renderer: fileUrlPicker.

Signature:

fields.fileUrl(): FileUrlFieldBuilder

object
anchor

Creates an object field for nested structures, lists, and dynamic zones. Value: Record<string, unknown> | null. Default renderer: objectAccordionSingle.

Signature:

fields.object(): ObjectFieldBuilder

See Object Fields for the full object-specific API.


Field Builder Methods
anchor

These chainable methods are available on all field builders returned by the field type factories.

label
anchor

Sets the field label.

Signature:

builder.label(text: string): this

description
anchor

Sets the description text displayed below the field.

Signature:

builder.description(text: string): this

help
anchor

Sets the help text.

Signature:

builder.help(text: string): this

note
anchor

Sets a supplementary note.

Signature:

builder.note(text: string): this

placeholder
anchor

Sets the input placeholder text.

Signature:

builder.placeholder(text: string): this

defaultValue
anchor

Sets the default value. Can be a static value or a function for dynamic defaults.

Signature:

builder.defaultValue(value: unknown): this

required
anchor

Marks the field as required.

Signature:

builder.required(message?: string): this

Parameters:

ParameterTypeRequiredDescription
messagestringNoCustom validation error message

requiredWhen
anchor

Conditionally marks the field as required based on other field values.

Signature:

builder.requiredWhen(fn: (form: IFormModel) => boolean, message?: string): this

Parameters:

ParameterTypeRequiredDescription
fnfunctionYesPredicate receiving the form model
messagestringNoCustom validation error message

schema
anchor

Sets a Zod validation schema for the field.

Signature:

builder.schema(zodSchema: z.ZodTypeAny): this

Parameters:

ParameterTypeRequiredDescription
zodSchemaz.ZodTypeAnyYesZod schema object

renderer
anchor

Overrides the default renderer for the field.

Signature:

builder.renderer(name: string, settings?: Record<string, unknown>): this

Parameters:

ParameterTypeRequiredDescription
namestringYesRenderer name
settingsRecord<string, unknown>NoRenderer-specific settings

options
anchor

Adds value options. Automatically switches text/number fields to the dropdown renderer.

Signature:

builder.options(opts: IValueOption[] | ((form: IFormModel) => IValueOption[])): this

Parameters:

ParameterTypeRequiredDescription
optsIValueOption[] \| functionYesStatic array or function returning options dynamically

Each IValueOption has: { label: string, value: string | number, disabled?: boolean }.


list
anchor

Converts the field to an array field. Automatically switches renderers (e.g., textInput to textInputs, objectAccordionSingle to objectAccordionMultiple).

Signature:

builder.list(): this

hidden
anchor

Hides the field from the UI. The value remains in the form data.

Signature:

builder.hidden(): this

disabled
anchor

Disables the field.

Signature:

builder.disabled(value?: boolean): this

rules
anchor

Sets conditional visibility and disable rules for the field. See Conditional Rules.

Signature:

builder.rules(rules: IRule[]): this

computed
anchor

Makes the field always computed — its value is recalculated whenever dependencies change.

Signature:

builder.computed(fn: (form: IFormModel) => unknown): this

computedUntilDirty
anchor

Makes the field computed until the user manually edits it.

Signature:

builder.computedUntilDirty(fn: (form: IFormModel) => unknown): this

beforeChange
anchor

Adds a transform that runs before a value change is applied. Return the transformed value.

Signature:

builder.beforeChange(fn: (value: unknown, form: IFormModel) => unknown): this

afterChange
anchor

Adds a callback that runs after a value change is applied.

Signature:

builder.afterChange(fn: (value: unknown, form: IFormModel) => void): this

afterSetValue
anchor

Adds a callback that runs after a programmatic setValue() call.

Signature:

builder.afterSetValue(fn: (value: unknown, form: IFormModel) => void): this

onBlur
anchor

Adds a callback that runs when the field loses focus.

Signature:

builder.onBlur(fn: (value: unknown, form: IFormModel) => void): this

cloneValue
anchor

Sets custom clone logic for when a list item containing this field is duplicated.

Signature:

builder.cloneValue(fn: (value: unknown) => unknown): this

tags
anchor

Tags the field for programmatic lookup.

Signature:

builder.tags(tags: string[]): this

Field Instance
anchor

The IField interface represents a runtime field instance, returned by form.field("name").

getValue
anchor

Gets the current field value.

Signature:

field.getValue<T = unknown>(): T

setValue
anchor

Sets the field value, triggering beforeChange and afterChange callbacks.

Signature:

field.setValue(value: unknown): void

setValueSilent
anchor

Sets the field value without triggering callbacks.

Signature:

field.setValueSilent(value: unknown): void

as
anchor

Casts the field to a typed field interface.

Signature:

field.as<T extends keyof FieldTypeMap>(type: T): FieldTypeMap[T]

Parameters:

ParameterTypeRequiredDescription
typestringYesField type to cast to (e.g. "object", "text")

validate
anchor

Validates the field against its schema, required status, and rules.

Signature:

field.validate(options?: { force?: boolean }): Promise<boolean>

blur
anchor

Triggers the blur event on the field.

Signature:

field.blur(): void

focus
anchor

Requests focus on the field.

Signature:

field.focus(): void

remove
anchor

Removes the field from the form model.

Signature:

field.remove(): void

setDisabled
anchor

Sets the disabled state of the field.

Signature:

field.setDisabled(value: boolean): void

setVisible
anchor

Sets the visibility of the field.

Signature:

field.setVisible(value: boolean): void

Properties
anchor

PropertyTypeDescription
namestringField name
typestringField type (e.g. "text", "number")
visiblebooleanWhether the field is visible
disabledbooleanWhether the field is disabled
qualifiedNamestringFull dot-notation path for nested fields
isComputedbooleanWhether the field is computed
vmIFieldVMView model for rendering

Object Fields
anchor

Object fields support nested structures, lists, and dynamic zones (templates). Access via form.field("name").as("object") or use the ObjectFieldBuilder.

Builder Methods
anchor

fields
anchor

Defines child fields for the object.

Signature:

builder.fields(fn: (fields: IFieldBuilderRegistry) => Record<string, IFieldBuilder>): this

template
anchor

Defines a dynamic zone template. Calling .template() automatically switches the renderer to dynamicZone.

Signature:

builder.template(id: string, configure: (t: ITemplateBuilder) => void): this

The ITemplateBuilder supports:

MethodDescription
.label(text)Template display name
.icon(icon)Template icon
.fields(fn)Define template fields
.visible(predicate)Conditional template visibility

listSchema
anchor

Adds a Zod validation schema for the list as a whole.

Signature:

builder.listSchema(schema: z.ZodTypeAny): this

Runtime Methods
anchor

These methods are available on IObjectField, accessed via form.field("name").as("object").

addItem
anchor

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>): void

removeItem
anchor

Removes an item from a list object field by index.

Signature:

objectField.removeItem(index: number): void

moveItem
anchor

Moves an item within a list object field.

Signature:

objectField.moveItem(fromIndex: number, toIndex: number): void

duplicateItem
anchor

Duplicates an item in a list object field.

Signature:

objectField.duplicateItem(index: number): void

setTemplate
anchor

Sets the active template on a single-value templated object field.

Signature:

objectField.setTemplate(templateId: string): void

Runtime Template Management
anchor

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
anchor

PropertyTypeDescription
isListbooleanWhether this is a list object field
isTemplatedbooleanWhether templates are defined
activeTemplateIdstring \| nullCurrently selected template ID
availableTemplatesITemplateVM[]Available templates
childrenMap<string, IField>Child fields (for single objects)
itemsIListItemField[]List items (for list objects)

Layout Builder
anchor

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
anchor

Arranges one or more fields in a horizontal row.

Signature:

layout.row(...fieldIds: string[]): IRowBuilder

separator
anchor

Adds a visual divider between layout sections.

Signature:

layout.separator(): ISeparatorBuilder

tabs
anchor

Creates a tabbed layout container.

Signature:

layout.tabs(id?: string): ITabsBuilder

The ITabsBuilder supports chaining:

MethodDescription
.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:

MethodDescription
.label(text)Tab label
.description(text)Tab description
.icon(icon)Tab icon
.layout(fn)Tab content layout
.rules(rules)Tab-level visibility rules

element
anchor

Adds a custom rendered element to the layout.

Signature:

layout.element(renderer: string, props?: Record<string, unknown>): IElementBuilder

object
anchor

Defines the inner layout for an object field.

Signature:

layout.object(fieldName: string, layout: (l: ILayoutBuilder) => ILayoutNodeBuilder[]): IObjectBuilder

Overload — per-template layouts for dynamic zones:

layout.object(fieldName: string, templateLayouts: Record<string, (l: ILayoutBuilder) => ILayoutNodeBuilder[]>): IObjectBuilder

Positioning
anchor

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
anchor

Rules control field visibility and disabled state based on other field values. Pass them via the .rules() builder method.

Rule structure:

PropertyTypeDescription
typestringRule type (use "condition")
targetstringField name to watch
operatorstringComparison operator (see below)
valuestring \| nullComparison value (null for unary operators)
actionstring"hide" or "disable"

Operators
anchor

OperatorDescription
"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
anchor

RendererField TypeSettingsDescription
textInputtextSingle-line text input
textareatext{ rows?: number }Multi-line text area
textInputstext (list){ addItemLabel?: string }List of text inputs
textareastext (list){ addItemLabel?: string }List of textareas
tagstext (list)Comma-separated tag input
codeEditortext{ language?: string, height?: number }Code editor with syntax highlighting
dropdowntext, numberSelect dropdown (auto when .options())
radioButtonstext, numberRadio button group
checkboxestext (list), number (list)Checkbox group
numberInputnumberNumber input
numberInputsnumber (list){ addItemLabel?: string }List of number inputs
switchbooleanToggle switch
dateTimeInputdatetime{ type, displayFormat?, yearRange?, weekStartsOn?, presets? }Date/time picker
dateTimeInputsdatetime (list){ type, displayFormat?, weekStartsOn?, addItemLabel? }List of date/time pickers
filePickerfileFile picker with full metadata
fileUrlPickerfileUrlFile picker returning URL only
objectAccordionSingleobject{ open?: boolean }Single object in accordion
objectAccordionMultipleobject (list){ open?, container?, itemTitle?, addItemLabel? }List of objects in accordions
dynamicZoneobject (templates){ container?: boolean }Template picker zone
passthroughobjectRenders child fields inline
keyValueTagsobject (list){ addItemLabel?: string }Key-value tag pairs
hiddenanyHidden field (no UI rendered)

Automatic Renderer Switching
anchor

  • Calling .options() on text/number fields switches to dropdown
  • Calling .list() on datetime switches to dateTimeInputs
  • Calling .list() on object switches to objectAccordionMultiple
  • Calling .template() on object switches to dynamicZone

Custom Renderers
anchor

createFieldRenderer
anchor

Creates a custom field renderer component.

Signature:

createFieldRenderer<TName extends string>(
    render: (props: { field: RendererField<TName> }) => React.ReactNode
): React.ComponentType<{ field: IFieldVM }>

createObjectFieldRenderer
anchor

Creates a custom renderer for object fields.

Signature:

createObjectFieldRenderer<TName extends string>(
    render: (props: { field: ObjectRendererField<TName> }) => React.ReactNode
): React.ComponentType<{ field: IFieldVM }>

FormView
anchor

The FormView component renders a form model in the UI.

Signature:

<FormView form={formModel.vm} renderers={fieldRenderers} layoutRenderers={layoutRenderers} />

Props:

PropTypeRequiredDescription
formIFormVMYesThe form view model
renderersFieldRenderersNoCustom field renderer overrides
layoutRenderersLayoutRenderersNoCustom layout renderer overrides