Askr documentation
Routing & Data

Page Actions and Forms

Page actions are POST handlers that return a redirect or field-level errors, and work with JavaScript disabled.

Example

Bind form input in the server action, return field-aware failures without navigation, and redirect only after a successful write.

<form method="post" action="/projects/new">
  <Field><FieldLabel htmlFor="name">Name</FieldLabel><Input id="name" name="name" required /></Field>
  <Button type="submit">Create project</Button>
</form>

Action contract

defineAction({ id, input, invalidates }) declares a named action: id identifies it, input is an ObjectSchema<TInput> from @askrjs/schema that validates incoming values, and an optional invalidates list names the query prefixes a successful submission should clear. What comes back is an ActionDescriptor — a plain object describing the contract, not a function you call directly.

Form submission

ActionForm({ action, children, ...props }) renders what the type declarations call a native form bound to a declared action — it's not a synthetic event API layered over fetch, it's a real <form> wired to the ActionDescriptor you pass in. For submitting programmatically instead of through a rendered form, action(descriptor) returns a command handle with a submit(input) method and a state tuple carrying ActionStatus (pending, result, error).

Validation failures

When submitted input fails the action's input schema, the failure comes back as an ActionValidationError with kind: 'invalid', the action id, the raw values that were submitted, an issues array from the schema validator, and fieldErrors — a record mapping each field name to its own array of messages. That per-field shape is what you'd use to highlight individual inputs rather than showing one generic error for the whole form.

Redirects and revalidation

Invalidation after a successful action is driven by the invalidates list on the ActionDescriptor, which clears the matching query prefixes the same way a mutation's affects() option does. Redirects, by contrast, do have a dedicated path through the actions module: the server-side ActionOutcome can carry a redirect location, which the enhanced JavaScript path (action()) validates as same-origin before calling location.assign() on it, and which an unenhanced native form submission turns into a real HTTP 303 response instead. It isn't ordinary router navigation wearing an action's clothes — it's a security-checked redirect pipeline that's part of the action contract itself.