cicadel
Documentation

Forms and validation

Define allowed fields, bind the submitted values, and act only after validation succeeds. For browser forms, keep session and CSRF middleware around the handler.

Ask your agent

text
Read https://cicadel.fauzaanu.com/en/dev/topics/forms-completion/index.md.
Add a form for [fields and validation rules] to my application.
Show field errors beside the controls and preserve non-sensitive submitted values.
Use the existing sessions and CSRF middleware. Test valid input, invalid input,
missing CSRF tokens and unexpected fields before connecting any side effects.

Define fields

Create the definition once during application setup. Handle configuration errors before serving requests.

go
package contact

import "github.com/fauzaanu/cicadel/forms"

func Definition() (*forms.Definition, error) {
    return forms.New([]forms.Field{
        forms.Text(forms.Options{
            Name: "email", Label: "Email", Widget: "email", Required: true,
        }, forms.Email),
        forms.Boolean(forms.Options{
            Name: "accepted", Label: "I agree to the terms",
        }, forms.Accepted),
    })
}

Only declared fields are bound. forms.Integer, forms.Number, forms.Date, forms.Select, and forms.Multiple cover other common inputs.

Bind a submission

In your POST handler, after authentication and CSRF checks:

go
r.Body = http.MaxBytesReader(w, r.Body, 8<<10)
posted, err := forms.Parse(r)
if err != nil {
    response.Error(w, http.StatusBadRequest)
    return
}
bound := definition.Bind(posted.Values)
if !bound.Valid() {
    // Render your form page with bound and status 422.
    renderForm(w, r, http.StatusUnprocessableEntity, bound)
    return
}
email, ok := forms.Value[string](bound, "email")
if !ok {
    response.Error(w, http.StatusInternalServerError)
    return
}
// Use email for the application's next step only after validation succeeds.

Here definition is the result of Definition(), and renderForm is your page-rendering helper. For a GET, render definition.Initial() instead. Do not log submitted values or redisplay passwords.

Render labels and errors

This component renders the bound fields. Pass a CSRF token obtained from your existing session middleware.

templ
package contact

import "github.com/fauzaanu/cicadel/forms"
import "github.com/fauzaanu/cicadel/ui"
import "github.com/fauzaanu/cicadel/ui/form"

templ fields(bound *forms.Bound, csrfToken string) {
    <form method="post" action="/contact">
        @form.CSRF.Call(form.CSRFProps{Token: csrfToken}, ui.NoSlots{})
        for _, field := range bound.Fields() {
            @form.Field.Call(form.Bound(field, "contact-" + field.Name), ui.NoSlots{})
        }
        <button type="submit">Submit</button>
    </form>
}

The field component connects labels, help text and errors to the control. Display form-wide errors separately using bound.Errors()[""]. The component guide shows a complete handler and page-rendering flow.

Save a model

Use model forms when accepted values should create or update a GORM model. Define an editable-field allowlist and scope every update to records the current user may change.

Stale edits

For editable records, add a Revision int64 column with a recorded migration, set model.Config[T].VersionField to "Revision", and render form.Version(ctx, &record) in a hidden _version field. Return a conflict message when a save returns model.ErrConflict; ask the user to reload before trying again.

File fields and persistence

Use forms/upload for bounded multipart uploads. Close each parsed batch, choose trusted storage keys, and connect storage writes to your persistence operation. The upload reference covers limits, validation, cleanup and middleware order.

Admin integration

Use admin.Config[T].Model to supply model-form fields and validation. Use the first-party formsets module for multiple rows and inline editing. See the forms reference for field types, custom validators and transaction behavior.

↑

Type to search this version.