Forms and Auth

Build forms that work in both live and plain HTTP fallback modes, then layer Vango auth and uploads on top correctly.

#forms#auth#uploads#toasts

Forms and Auth

Forms in Vango should work in two modes:

  • interactive live mode

  • plain HTTP fallback mode

Server validation is authoritative in both.

Route forms with setup.RouteForm

This is the preferred pattern for page-owned form posts.

go
1type SignupInput struct {
2	Email    string `form:"email" validate:"required,email"`
3	Password string `form:"password" validate:"required,min=12"`
4}
5
6func Action(ctx vango.ActionCtx, in SignupInput) (*vango.FormResult, error) {
7	if err := createAccount(ctx.StdContext(), in); err != nil {
8		return nil, err
9	}
10	return vango.RedirectTo("/welcome"), nil
11}
12
13func SignupScreen() vango.Component {
14	return vango.Setup(vango.NoProps{}, func(s vango.SetupCtx[vango.NoProps]) vango.RenderFn {
15		form := setup.RouteForm(&s, SignupInput{})
16
17		return func() *vango.VNode {
18			return form.View(
19				form.Field("email", Input(Type("email"))),
20				form.Field("password", Input(Type("password"))),
21				Button(Type("submit"), Disabled(form.IsSubmitting()), Text("Create account")),
22			)
23		}
24	})
25}

Rules:

  • use it when the form belongs to the current route

  • form.View(...) renders the actual <form> and injects CSRF automatically

  • parse errors and struct-tag validation rerender with raw values preserved

  • use vango.Invalid(...) for business-rule validation after decoding succeeds

Manual form pattern

Manual signals plus setup.Action are the escape hatch for tiny bespoke forms.

Use this only when typed form helpers are not buying you much.

Typed forms with setup.Form

Use setup.Form for session-driven forms, dialogs, and custom submit transports.

go
1type ContactFormData struct {
2	Name    string `form:"name" validate:"required,min=2,max=100"`
3	Email   string `form:"email" validate:"required,email"`
4	Message string `form:"message" validate:"required,max=1000"`
5}
6
7form := setup.Form(&s, ContactFormData{})

Common helpers:

  • Field(...)

  • Values()

  • Validate()

  • Errors()

  • FieldErrors(...)

  • SetError(...)

  • Reset()

  • Array(...)

  • ArrayKeyed(...)

Use setup.Form when the transport is not a route-bound page Action.

Dynamic form arrays

Use ArrayKeyed for reorderable or filterable arrays so identity stays stable.

Use plain Array only when order is static and identity does not matter.

Low-level parsing

Low-level form parsing APIs are escape hatches for custom HTTP handlers, APIs that intentionally accept form bodies, or unusual upload flows.

They are not the default path for normal page forms.

Auth model

Vango auth has two layers:

  1. HTTP layer validates the request

  2. session layer carries the auth projection during live interaction

The standard pattern is:

  • HTTP middleware authenticates the request

  • request context carries the user or principal

  • OnSessionStart and OnSessionResume rehydrate auth state for the live session

  • component code reads auth through the auth package

The auth bridge

Middleware authenticates the HTTP request. Session hooks project that into the live session.

Read auth in app code with helpers such as:

  • auth.Get[T](ctx)

  • auth.Require[T](ctx)

Route auth middleware

Use pkg/authmw for route-level protection.

Common helpers:

  • authmw.RequireAuth

  • authmw.RequireRole(...)

  • authmw.RequirePermission(...)

  • authmw.RequireAny(...)

  • authmw.RequireAll(...)

Auth freshness

Long-lived sessions need freshness checks.

  • use periodic auth checks in session config

  • use ctx.RevalidateAuth() before sensitive mutations

  • fail closed when auth becomes invalid

Do not store raw provider credentials or tokens in persisted app state.

Uploads

Uploads happen over HTTP, not over the live event channel.

Recommended pattern:

  • mount a dedicated upload endpoint

  • enforce CSRF, size limits, and type limits

  • keep temp uploads private

  • upload first, claim later

Toasts

Use pkg/toast for live feedback:

  • toast.Success(...)

  • toast.Error(...)

  • toast.Warning(...)

  • toast.Info(...)

  • toast.WithTitle(...)

Toast events are emitted through ctx.Emit("vango:toast", ...), so the UI that renders them is up to your app.

Next, read Client Boundaries before introducing browser-owned behavior.