Actions are endpoint declarations. A page declares the exported same-package Go
symbol, HTTP method, and endpoint path in .gwdk; normal Go owns the behavior.
The supported declaration shape is:
package auth
act Submit POST "/signup" @error "/errors/signup.html"
Current behavior:
act Submit POST "/signup"binds exactly to exported Go functionSubmitin a same-package.gofile or defaultgo {}block.- The optional endpoint-local
@errorsuffix selects a generated HTML error page for action panics before response headers are written. Returned handler errors still follow normalruntime/response.Responsebehavior. - Old
act submit { ... }blocks are rejected with a migration diagnostic. - Actions currently require
POST. - Redirects, fragments, validation, and business rules come from the Go handler
response, not from generated
.gwdkaction body code. <form g:post={Submit}>lowers to a standard POST form for a supported action.gowdk build --app --bingenerates POST handlers for non-dynamic page routes. If a same-directory Go package exports a matching handler function, the generated app decodes direct literal fields from same-pageg:postforms, validates supported literal form constraints, calls that function, and writes itsruntime/response.Response.Submitmust use one of the supported signatures:func(context.Context) (response.Response, error),func(context.Context, SignupInput) (response.Response, error),func(context.Context, *SignupInput) (response.Response, error), orfunc(context.Context, form.Values) (response.Response, error).- In development/default mode, missing or unsupported action handlers are not build errors. Generated apps return HTTP 501 with a clear message for those routes.
- In production mode, explicitly declared actions must bind to supported
same-package Go handlers. Missing or unsupported handlers fail the build
unless
Build.AllowMissingBackendor--allow-missing-backendis set to intentionally generate HTTP 501 stubs during a migration. - Generated first-slice input decoders create a named input wrapper, preserve repeated submitted values, allow missing fields, and reject unexpected fields with HTTP 400.
- Generated handlers enforce direct literal
required,minlength,maxlength, andpatterncontrols for typed action forms when generated validation is enabled. Normal validation failures return HTTP 422. - Generated first-slice action error responses use explicit status mapping for
invalid CSRF tokens, invalid forms, oversized requests, and validation
failures, and set
Cache-Control: no-store. - Generated typed action decoders are built from same-package Go AST metadata,
then printed as ordinary Go code. They decode exported struct fields using
form:"name"tags first, then Go field names. They ignoreform:"-", reject unknown user fields through the generated allowlist step, strip generated runtime fields such as_gowdk_csrf, supportstring,[]string,bool, signed integers, and unsigned integers, reject repeated scalar fields, leave missing or blank numeric/boolean fields as zero values, and return structured errors without submitted values. runtime/appexposes backend helpers for generated adapters:BackendRouter,Action0,ActionForm[T],ActionFormPtr[T],ActionValues,APIHandler, andNotImplemented. These helpers usecontext.Contextplusapp.Request(ctx),app.Params(ctx),app.CSRF(ctx),app.Session(ctx),app.Route(ctx), andapp.Endpoint(ctx)instead of a custom GOWDK context type.- Generated bound action adapters attach endpoint metadata to the handler
context. User handlers can call
app.Endpoint(ctx)to read the generated endpoint kind, page ID, symbol name, method, and path without importing generated app code. - Generated action and API request-time lanes run inside a runtime panic
boundary. A panic before response headers are written becomes a no-store
HTTP 500 response without exposing the panic value. Ordinary returned errors
still use the existing
response.HandlerErrorstatus contract. - Feature packages that declare action handlers may import stable public GOWDK
packages such as
runtime/response,runtime/form, andruntime/app; they must not import generated app packages, generatedgowdkappoutput, generatedcmd/servercode, or build output directories. Generated app source imports feature packages, never the other way around. internal/appgenemits the generated action adapter glue used by generated apps; user action behavior remains in normal same-package Go handlers.addons/actions.ValidateRequiredexposes the same required-field behavior as aruntime/validation.Resultfor addon and generated-handler integrations.addons/actions.NewCSRFprovides signed double-submit CSRF tokens with an HttpOnly, Secure, SameSite=Lax cookie by default. Normal builds do not expose a no-op CSRF validator; package tests keep their no-op helper in_test.go.Build.CSRF.Enabledwires generated CSRF token generation and validation for generated action adapters. Generated apps read the signing secret fromBuild.CSRF.SecretEnvorGOWDK_CSRF_SECRET, inject a hidden token field into served HTML POST forms, validate action POSTs before generated decoding or user handlers run, and return HTTP 403 withinvalid csrf tokenplusCache-Control: no-storefor missing or invalid tokens.- Field inference currently reads direct
input,textarea,select, and named submit controls with literalnameattributes; fields hidden inside component calls are not inferred yet. - User Go handlers that accept
form.Valuescan decode form controls with runtime helpers:form.Select,form.SelectMultiple,form.Radio,form.Checkbox, andform.CheckboxGroup. Single checkboxes decode absent as false and repeated checkbox values are reserved for explicit groups. - Direct
input type="file"controls and multipartg:postforms are rejected during generated app action endpoint extraction. Uploads belong in user-owned API/server handlers where body limits, storage, validation, cleanup, auth, and logging policy are explicit. - Actions declared on guarded pages share the generated app guard hooks with
SSR pages and APIs. Custom guards require
GOWDKGuardRegistry; native RBAC guard IDs such asrole:adminandpermission:posts.writerequireGOWDKAuthProvider. Missing backing hooks fail the generated app Go build. Generated action handlers run guards before CSRF checks, form decoding, and user handler calls. Treat these as defense-in-depth redundancy for generated route/page access, never as backend resource authorization. If the page itself is protected, use request-time page rendering; build-time SPA HTML cannot enforce frontend page access. - Form values are not logged.
The current compiler-generated same-package action binding can decode direct
literal form fields into exported same-package user input structs for supported
typed action signatures and can wire generated CSRF when Build.CSRF.Enabled
is set. Generated validation failures return HTTP 422 for normal requests; for
partial requests with X-GOWDK-Partial and X-GOWDK-Target, generated handlers
return an escaped runtime/response.ValidationFragment for the target instead.
Generated pattern checks use GOWDK's anchored form-pattern subset: literals,
., character classes/ranges, grouping, alternation, common escapes such as
\d, \w, and \s, and *, +, ?, {n}, {n,}, and {n,m}
quantifiers. GOWDK does not run user-defined domain validation or generate
general fragment routes. Handlers can return redirects, fragments, HTML, or JSON
through runtime/response.Response.
Production Notes
- Enable
Build.CSRF.Enabledfor generated app deployments that accept action POSTs, and provide a stable runtime secret throughBuild.CSRF.SecretEnvorGOWDK_CSRF_SECRET. - Keep authentication, backend authorization, business validation, persistence,
service calls, redirects, HTML, JSON, and fragment decisions in normal Go handlers.
Generated adapters decode the request and write the returned
runtime/response.Response; they do not generate application policy. - Generated checks only cover direct literal
required,minlength,maxlength, and supportedpatterncontrols in the currentview {}subset. Treat them as request-shape checks, not a substitute for domain validation in Go. Optional empty fields skip length and pattern checks, matching browser constraint behavior. Partial validation failures use an escaped validation fragment so the client runtime can swap the configured target. - Direct literal controls can customize those generated request-shape messages
with
g:message:required,g:message:minlength,g:message:maxlength, andg:message:pattern. Each message attribute must be a literal string and must appear on a control that declares the matching constraint. runtime/response.ValidationJSONandruntime/response.ValidationFragmentprovide reusable patterns for returning structured validation errors or an escaped live-region fragment for partial form updates.- Generated action redirects must stay local. User handlers should also keep redirects local unless they intentionally implement and audit an external redirect allowlist.
- Generated action, validation, redirect, fragment, invalid-form, oversized
body, missing-handler, unsupported-handler, and invalid-CSRF responses use
Cache-Control: no-store. - Handler errors are written with
runtime/response.HandlerStatus, defaulting to HTTP 500 when the error does not carry a safer explicit status. Do not put sensitive submitted values in error messages. - File uploads are intentionally not generated by GOWDK actions. Implement uploads in user-owned API/server handlers with explicit body limits, storage, validation, cleanup, auth, and logging rules.
Forms
Current form behavior is intentionally narrow and literal-analysis driven:
- Forms post only when they declare
g:post={Action}and the action exists on the same page. - SPA builds lower
g:posttomethod="post"and the current concrete page route. - Field inference reads direct
input,textarea,select, and named submit controls with literalnameattributes. - Named submit controls such as
<button name="intent" value="save">and<input type="submit" name="intent">are treated as explicit submit-intent fields before unknown-field rejection. Non-submitting controls such astype="button"andtype="reset"are ignored. - Validation is generated only from direct literal controls with
required,minlength,maxlength, orpattern. Dynamic constraint attributes are rejected for generated validation metadata. Literalg:message:*attributes can customize generated validation messages for matching constraints. - Generated first-slice decoders preserve repeated submitted values, allow missing fields, reject unexpected fields, and avoid logging form values.
- Generated typed action decoders reject repeated scalar fields and support
repeated values only for
[]string. input type="file"and multipartg:postforms are rejected; uploads are user-owned API/server behavior.- Component-hidden fields are not inferred yet.
Partial form metadata can be added to a supported action form:
<form g:post={Refresh} g:target="#patients" g:swap="innerHTML">
<input name="query" />
<button>Refresh</button>
</form>
g:target must be a literal id selector present in the same direct view {}
markup subset. Current swap modes are innerHTML and outerHTML.
Future action behavior must define:
- Redirect safety beyond local redirect validation.
- Error response shape and HTTP status mapping for broader generated action execution.
For the no-JavaScript baseline, enhanced fragment behavior, invalidation boundary, and upload ownership rules, see forms.md.