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
آخر نشاط في المصدر
٢٠ أكتوبر ٢٠٢٦ في ١٢:٠١
لغة SKILL.md المكتشفة
الإنجليزية
النجوم
٨٬٠٤٨
التفرعات
٦٨٣

خيارات التثبيت

يُحدَّد 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"` | Holidays, due dates (birthdays: see the birthday picker below) | | `.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" .dateGte("2026-01-01", "message?") // value must be on or after .dateLte({ expression: "today-18y" }, "message?") // value must be on or before ``` `.dateGte()` and `.dateLte()` take a fixed value, or, on a date-only field, `{ expression }` worked out from the editor's local date every time the field is checked. An expression is `today`, or `today` plus or minus a number of days (`d`), months (`m`) or years (`y`): `"today-18y"`, `"today+1d"`. The limits survive a later `.renderer()` or `.schema()` call, and the check runs after the field's own schema. In the message, `{date}` is the limit in the admin's date format; without a message, the field says "Must be on or after {date}." or "Must be on or before {date}.". For dates far in the past, like a date of birth, a date-only field can use the `birthdayInput` renderer, a step-by-step picker (day, then month, then year). It stores the same `"YYYY-MM-DD"` value as the calendar, and has no settings: it offers only the dates the field's `dateGte`/ `dateLte` limits allow (without limits, this year back 120 years). Chain `.renderer()` after the subtype method, since subtype methods reset the renderer: ```typescript fields.datetime().dateOnly().renderer("birthdayInput"); fields .datetime() .dateOnly() .dateGte({ expression: "today-100y" }) // not older than 100 .dateLte({ expression: "today-18y" }, "You must be 18 or older.") .renderer("birthdayInput"); ``` ### 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 |
عرض على GitHub
ملف SKILL.md هذا كبير جدا، لذلك يعرض SkillsMP القسم الاول فقط هنا. عرض على GitHub