Files
summercms/docs/backend/forms.md
Jakub Zych 44bd1446f5 feat(11.1-04): add the Backend section, the remaining Services pages and the concept map links
- docs/backend: admin controllers, forms, lists and filters, relation
  manager, users and permissions, settings, partials and widgets, admin SPA
- docs/services: storage, outbound HTTP, realtime, Web Push, search, parity
  testing and the Frontend and AJAX (not provided) page
- Examples for cabana (with testdata/docs YAML), fetchguard, lighthouse and
  its centrifugo driver, flare, beachcomber and typesense, tide; lighthouse
  and beachcomber TestDocs* regions run on their Postgres harnesses
- concept map rows link their guide pages and the not-provided rows the
  Frontend and AJAX page; index lists Backend, Database and Services
- TestDocsRequiredPages asserts the D-08 section order
2026-09-30 23:18:35 +02:00

5.1 KiB

title, description, section, order
title description section order
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. backend 20

Forms

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:

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

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:

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.