Backend

Forms

Describe admin forms in config_form.yaml and fields.yaml, with the supported field types, spans, tabs, dropdown options and create or update contexts.

On this page

The Form behaviour's config_form.yaml and the model's fields.yaml keep their WinterCMS shape. cabana 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:

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/<vendor>/<plugin>/ prefix points into the plugin's own embedded tree.

fields.yaml#

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:

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 An embedded list of related records; see Relation manager.
widget A plugin custom element with a server action; see Partials and widgets.
partial A server-rendered template; see Partials and widgets.

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). 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.