--- title: Forms description: Describe admin forms in config_form.yaml and fields.yaml, with the supported field types, spans, tabs, dropdown options and create or update contexts. section: backend order: 20 --- # Forms The Form behaviour's `config_form.yaml` and the model's `fields.yaml` keep their WinterCMS shape. [cabana](../../modules/cabana/README.md) compiles them strictly at boot: an unknown key, field type, span or size stops the start-up with an error naming the file and the field, so a WinterCMS option that SummerCMS does not implement is never ignored silently. ## config_form.yaml The controller's form configuration names the fields file with a WinterCMS path, the model class, and where the SPA goes after a save: ```yaml src=modules/cabana/testdata/docs/controllers/posts/config_form.yaml name: acme.blog::lang.posts.form form: ~/plugins/acme/blog/models/post/fields.yaml modelClass: Post defaultRedirect: acme/blog/posts create: redirect: acme/blog/posts/update/:id redirectClose: acme/blog/posts update: redirect: acme/blog/posts redirectClose: acme/blog/posts ``` `modelClass` must equal the controller's `pact.AdminController.ModelName`. The `~/plugins///` prefix points into the plugin's own embedded tree. ## fields.yaml ```yaml src=modules/cabana/testdata/docs/models/post/fields.yaml fields: title: label: acme.blog::lang.posts.title_column type: text span: left required: true slug: label: acme.blog::lang.posts.slug type: text span: right context: update comment: acme.blog::lang.posts.slug_comment status: label: acme.blog::lang.posts.status type: dropdown span: left options: draft: acme.blog::lang.posts.draft published: acme.blog::lang.posts.published published: label: acme.blog::lang.posts.published type: switch span: right content: label: acme.blog::lang.posts.content type: textarea size: large tab: acme.blog::lang.posts.tab_content ``` The compiled schema keeps the fields in file order, with their labels as translation keys until a request asks for them in its locale: ```go src=modules/cabana/example_test.go#ExampleCompileForm fsys := os.DirFS("testdata/docs") form, err := cabana.CompileForm("acme.blog", PostsController{}, fsys) if err != nil { fmt.Println(err) return } for _, f := range form.Fields { fmt.Printf("%s %s span=%q tab=%q required=%v options=%d\n", f.Name, f.Type, f.Span, f.Tab, f.Required, len(f.Options)) } // Output: // title text span="left" tab="" required=true options=0 // slug text span="right" tab="" required=false options=0 // status dropdown span="left" tab="" required=false options=2 // published switch span="right" tab="" required=false options=0 // content textarea span="" tab="acme.blog::lang.posts.tab_content" required=false options=0 ``` ### Field types | Type | Renders | |------|---------| | `text`, `textarea`, `number` | Text inputs. | | `checkbox`, `switch` | Booleans. | | `dropdown` | A select. Options are a map in the YAML, or the name of a method the controller answers through `pact.DropdownOptionsProvider`. | | `relation` | A belongsTo or belongsToMany picker; see [Relation manager](relation-manager.md). | | `relation-manager` | An embedded list of related records; see [Relation manager](relation-manager.md). | | `widget` | A plugin custom element with a server action; see [Partials and widgets](partials-and-widgets.md). | | `partial` | A server-rendered template; see [Partials and widgets](partials-and-widgets.md). | The WinterCMS widgets that are not in this list (the rich editor, the media finder, the repeater, the file upload and the others) are not provided. A field with one of those types stops the start-up. ### Field options A field takes `label`, `comment`, `type`, `required`, `default`, `tab`, `span` (`left`, `right`, `full`, `auto`, `row`), `size` (`tiny`, `small`, `large`, `huge`, `giant`), `context`, `attributes` (scalar HTML attributes for the input), `options` and `emptyOption`, plus `nameFrom` and `relation` on relation fields. WinterCMS keys outside this set, such as `readOnly`, `disabled`, `trigger` or `dependsOn`, are refused. `context: update` shows a field only on the update form, and `context: create` only on the create form; a list of contexts is also accepted. The context is enforced on the server too: a field that is hidden on a form is never written by that form's save, whatever the request body holds. ## What a save may write The form's writable fields are bound to model columns at boot. A save passes only those fields that are also in the model's `Fillable` list, drops unknown keys, case variants and nested objects, and fills the model with `lagoon.Fill` (see [Models](../database/models.md)). The model's validation rules (a `Rules` method returning `lagoon.Validate` rule strings) and the form's `required` flags are checked in the save's transaction, and a failure is a 422 with messages per field. A value that does not fit its column is also a 422 on that field.