Skip to main content

webiny-form-model

Building forms with the FormModel system — field types, renderers, layout, validation, conditional rules, computed fields, and dynamic zones. Use this skill when the developer needs to define form fields with the builder API, choose renderers, build layouts with tabs/rows/separators, add validation (Zod or imperative), use conditional visibility/disable rules, create computed fields, or work with object fields and templates (dynamic zones).

来源信息

仓库
webiny/webiny-js
最近来源活动
2026年7月24日 12:20
检测到的 SKILL.md 语言
英语
星标
8,045
分支
682

安装方式

默认使用会先检查来源的 Prompt;你也可以切换为直接命令,或下载本地副本。

检查来源文件

决定是否安装前,请先阅读 SKILL.md,以及 SkillsMP 当前展示的配套文件。

正在显示 SKILL.md

SKILL.md
来源说明 · 只读预览
name
webiny-form-model
description
Building forms with the FormModel system — field types, renderers, layout, validation, conditional rules, computed fields, and dynamic zones. Use this skill when the developer needs to define form fields with the builder API, choose renderers, build layouts with tabs/rows/separators, add validation (Zod or imperative), use conditional visibility/disable rules, create computed fields, or work with object fields and templates (dynamic zones).
# Form Model ## TL;DR The Form Model is Webiny's declarative form system. Define fields with a fluent builder API (`fields.text()`, `fields.datetime()`, etc.), arrange them with a layout builder (`layout.row()`, `layout.tabs()`, etc.), and validate with Zod schemas or imperative rules. Fields support conditional visibility, computed values, reactive context from other fields (`.context()`), and deeply nested object/list structures with templates (dynamic zones). ## Field Types All fields are created via the `fields` registry callback. Each builder method returns a chainable builder. ### Text ```typescript fields.text(); ``` Default renderer: `textInput`. Value: `string | null`. ```typescript fields.text().label("Title").placeholder("Enter title").required("Title is required") fields.text().renderer("textarea", { rows: 4 }) fields.text().list().renderer("tags").defaultValue([]) fields.text().list().renderer("textInputs", { addItemLabel: "Add text" }) fields.text().list().renderer("textareas", { addItemLabel: "Add description" }) fields.text().renderer("codeEditor", { language: "html", height: 300 }) fields.text().options([ { label: "Option A", value: "a" }, { label: "Option B", value: "b" } ]) // auto-switches to "select" renderer fields.text().options([...]).renderer("radioButtons") fields.text().list().options([...]).renderer("checkboxes") // Dynamic options — callback receives { field, form }, re-evaluated reactively fields.text().options(({ form }) => { const type = form.field("general.type").getValue(); return getOptionsForType(type); }) ``` ### Number ```typescript fields.number(); ``` Default renderer: `numberInput`. Value: `number | null`. Auto-normalizes to number. ```typescript fields.number().label("Count").placeholder("0").required(); fields.number().list().renderer("numberInputs", { addItemLabel: "Add number" }); fields.number().options([ { label: "Tier 1", value: 100 }, { label: "Tier 2", value: 200 } ]); ``` ### Boolean ```typescript fields.boolean(); ``` Default renderer: `switch`. Value: `boolean | null`. ```typescript fields.boolean().label("Featured").defaultValue(false); ``` ### DateTime ```typescript fields.datetime(); ``` Default renderer: `dateTimeInput`. Pick a variant method to set the subtype: | Variant | Value Format | Example | | ----------------------------------------- | ------------------------------ | ----------------------- | | `.dateOnly()` | `"2026-05-01"` | Birthdays, due dates | | `.timeOnly()` | `"14:30:00"` | Opening hours | | `.withTimezone()` | `"2026-05-01T14:30:00+02:00"` | Events tied to a locale | | `.withoutTimezone()` | `"2026-05-01T14:30:00.000Z"` | Timestamps, logs | | `.monthOnly()` | `"2026-05"` | Billing cycles | | `.weekOnly({ startsOn: 1 })` | `"2026-W18"` | Sprint planning | | `.yearOnly({ range: [2020, 2035] })` | `2026` (number) | Fiscal years | | `.dateRange()` | `{ from: "...", to: "..." }` | Vacation requests | | `.multipleDates()` | `["2026-05-01", "2026-05-03"]` | Blackout dates | | `.multipleMonths()` | `["2026-01", "2026-03"]` | Seasonal availability | | `.multipleYears({ range: [2020, 2035] })` | `[2024, 2025, 2026]` | Multi-year budgets | Additional chainable methods: ```typescript .presets([ { label: "Today", value: () => new Date() }, { label: "In a week", value: () => addDays(new Date(), 7) } ]) .displayFormat("dd/MM/yyyy") // date-fns format tokens .list() // switches renderer to "dateTimeInputs" ``` ### File ```typescript fields.file(); ``` Default renderer: `filePicker`. Value: `FileValue | null` (object with `id`, `name`, `size`, `mimeType`, `src`, `width`, `height`). ```typescript fields.file().label("Image"); ``` ### File URL ```typescript fields.fileUrl(); ``` Default renderer: `fileUrlPicker`. Value: `string | null` (URL only). ```typescript fields.fileUrl().label("Image URL"); ``` ### Lexical ```typescript fields.lexical(); ``` Default renderer: `lexical`. Value: `RichTextValueWithHtml | null` (`{ state: string; html: string }`). ```typescript fields.lexical().label("Content").required("Content is required"); ``` ### Password ```typescript fields.password(); ``` Default renderer: `passwordInput`. Value: `string | null`. ```typescript fields.password().label("Password").required("Password is required"); ``` ### Permissions ```typescript fields.permissions(); ``` Default renderer: `permissions`. Value: `Record<string, unknown>[]`. Has a built-in Zod schema requiring at least one permission entry. ```typescript fields.permissions().label("Permissions"); ``` ### Roles Multi-Select ```typescript fields.rolesMultiSelect(); ``` Default renderer: `rolesMultiSelect`. Value: `unknown[]`. ```typescript fields.rolesMultiSelect().label("Roles"); ``` ### Object ```typescript fields.object(); ``` Default renderer: `objectAccordionSingle`. For nested structures, lists, and dynamic zones. ```typescript // Simple nested object fields.object().label("Address").fields(f => ({ street: f.text().label("Street"), city: f.text().label("City"), zip: f.text().label("ZIP") })) // List of objects fields.object().list().label("Authors").fields(f => ({ name: f.text().label("Name").required(), email: f.text().label("Email") })) // Dynamic zone (single template selection) fields.object().label("Content Block") .template("hero", t => { t.label("Hero Banner") .icon({ type: "icon", name: "fas/image" }) .fields(f => ({ heading: f.text().label("Heading").required(), image: f.file().label("Image") })); }) .template("text", t => { t.label("Rich Text").fields(f => ({ body: f.text().label("Body").renderer("textarea") })); }) // Dynamic zone list (multiple items, each picks a template) fields.object().list().label("Page Sections") .renderer("dynamicZone", { container: false }) .template("hero", t => { ... }) .template("cta", t => { ... }) // Key-value list fields.object().list().label("Meta Tags") .renderer("keyValueTags", { addItemLabel: "Add tag" }) .fields(f => ({ name: f.text().placeholder("Name"), content: f.text().placeholder("Content") })) ``` Template visibility can be conditional: ```typescript .template("premium", t => { t.label("Premium Widget") .visible(form => form.field("plan").getValue() === "enterprise") .fields(f => ({ ... })); }) ``` ## Common Builder Methods These are available on **all** field types: | Method | Description | | ----------------------------- | ------------------------------------------------------- | | `.label(text)` | Field label | | `.description(text)` | Description text below the field | | `.help(text)` | Help text | | `.note(text)` | Supplementary note | | `.placeholder(text)` | Input placeholder | | `.defaultValue(value)` | Default value (can be a function for dynamic defaults) | | `.required(message?)` | Mark as required | | `.requiredWhen(fn, message?)` | Conditionally required based on other field values | | `.schema(zodSchema)` | Zod validation schema | | `.renderer(name, settings?)` | Override the default renderer | | `.options([...])` | Add value options (auto-switches text/number to select) | | `.list()` | Convert to array field | | `.hidden()` | Hide the field (value still in form data) | | `.hiddenWhen(fn)` | Conditionally hide based on form state | | `.disabled(value?)` | Disable the field | | `.disabledWhen(fn)` | Conditionally disable based on form state | | `.rules([...])` | Conditional visibility/disable rules | | `.computed(fn)` | Always-computed value from other fields | | `.computedUntilDirty(fn)` | Computed until user edits the field |
在 GitHub 查看
这个 SKILL.md 很大,SkillsMP 这里只预览前一段内容。 在 GitHub 查看