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 ¶
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.
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:
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.
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.