GOWDK forms start as normal HTML forms. JavaScript can enhance a form into a fragment request, but Go handlers still own action behavior.
g:post={Submit} lowers to a standard POST form:
<form g:post={Submit}>
<input name="email" required />
<button>Subscribe</button>
</form>
The generated page remains usable without JavaScript. In generated apps, the
POST route decodes the declared request shape, validates supported literal
request-shape constraints, runs guards and CSRF when configured, calls the
same-package Go action handler, and writes the returned
runtime/response.Response.
There is no generated page-level form state object today. The submitted form
data, handler response, redirected page, or returned fragment is the source of
truth. Component state and g:bind can improve client interaction, but they do
not replace server validation or action results.
Full-page POST handlers return runtime/response.Response:
response.RedirectTo("/next")for POST/redirect/get.response.HTMLBody(status, body)for an explicit HTML response.response.JSONBody(status, body)orresponse.JSONValue(status, value)for JSON.partial.Fragment(target, body)orpartial.Swap(target, swap, body)for fragment responses.response.ValidationJSON(result)orresponse.ValidationFragment(target, result)for validation responses.response.ReloadPage()for enhanced forms that should reload the current page after the action completes.
Generated request-shape validation is intentionally narrow. It covers direct
literal form fields and literal constraints such as required, minlength,
maxlength, and pattern. Domain validation belongs in the Go handler.
A form with g:target opts into partial enhancement:
<form g:post={Refresh} g:target="#patients" g:swap="innerHTML">
<input name="query" />
<button>Refresh</button>
</form>
<section id="patients"></section>
The compiler lowers this to normal form attributes plus data-gowdk-*
metadata and emits assets/gowdk/gowdk.js when the page needs it. The runtime
submits the form with:
X-GOWDK-Partial: 1X-GOWDK-Target: <target>X-GOWDK-Swap: <swap>
Successful enhanced responses swap innerHTML or outerHTML into the target.
Before the partial POST, the runtime runs the browser's native constraint
validation (checkValidity / reportValidity) when available. Invalid
enhanced forms are not posted, gowdk:validation-blocked is dispatched on the
form, and the server remains authoritative for every request that reaches the
action handler. The runtime dispatches gowdk:before-request,
gowdk:after-swap, and gowdk:request-error, toggles aria-busy, preserves
focus where possible, and remounts generated islands around replaced DOM.
Failed enhanced requests dispatch gowdk:request-error with detail.status,
detail.body, and detail.response when an HTTP response exists.
Enhanced redirects are not a stable contract today. For enhanced requests, return a fragment response for the target. Use normal full-page POST redirects for the no-JavaScript path.
There is no nearest error-boundary lookup for enhanced actions today. Failed
enhanced requests dispatch gowdk:request-error; generated validation
fragments can target a declared error container such as #errors. Generated
validation fragments are escaped live regions with role="alert" and
aria-live="polite".
GOWDK does not automatically invalidate page data after actions. Action handlers choose the lifecycle outcome explicitly.
Use one of these explicit outcomes:
- Redirect after full-page POST so the browser loads fresh page output.
- Return a fragment response for the changed region.
- Return
response.ReloadPage()so enhanced forms reload the current page and rerun request-timeserver {}data. - Return JSON to a user-owned client integration.
- Call an app-owned API or reload policy outside generated core.
Generated JavaScript must not own routing, auth, business rules, database access, server validation, action behavior, global app state, or page loading policy.
The generated first slice infers direct input, textarea, select, and
named submit controls with literal name attributes. It does not infer fields
hidden inside component calls.
Author-written form controls keep their literal browser validation attributes
such as required, type, inputmode, min, and max. When a g:post
action has bound Go input metadata, the renderer can add missing numeric browser
attributes to direct literal <input name="..."> controls: type="number",
inputmode="numeric", min="0" for unsigned integers, and exact min/max
bounds for sized integer types. It does not synthesize required because
requiredness is enforced from literal form constraints, not from Go field type
metadata.
Multipart action uploads use normal HTML forms with explicit generated policy:
<form g:post={UploadAvatar} enctype="multipart/form-data">
<input name="avatar" type="file" required
g:max-file-size="1048576"
g:max-files="1"
accept="image/png,image/jpeg" />
<button>Upload</button>
</form>
Each direct file control must declare literal g:max-file-size, g:max-files,
and MIME accept entries. Exact MIME types and type/* wildcards are allowed;
extension-only accept entries such as .png are rejected because generated
server policy checks submitted content types. The generated action adapter
parses multipart requests under Build.BodyLimits.ActionBytes, enforces the
per-file policy, preserves CSRF behavior, and decodes uploads into form.File
or []form.File fields on typed action input structs. Low-level handlers can
accept form.Data.
Storage, content scanning, persistence, cleanup beyond parser temporary files,
authorization, and domain validation remain user-owned Go behavior. Stream file
content with file.Open() during the request.