Form posts
form: true lets one function serve two kinds of caller: the stub, and a plain HTML <form method="post"> that works before any script has loaded.
form: true lets one function serve two kinds of caller: the stub, and a plain HTML <form method="post"> that
works before any script has loaded. For a POST whose content type is application/x-www-form-urlencoded or
multipart/form-data:
- The
x-dusk-fnheader and the JSON content type are not required (a browser form cannot send them). The CSRF check still applies, and a form post carriesSec-Fetch-SiteandOrigin. - The input is
formToObjectof the form data: a null-prototype object, repeated fields as arrays, files asFile, and the keys__proto__,constructorandprototypedropped. Every value is a string or aFile, so the schema converts numbers and booleans itself. A field name that is not valid UTF-8 (the parser would turn it into U+FFFD, and two different names would become one field) is a 400, as is a body that does not parse (no boundary, truncated). - The answer is never JSON. Success redirects with 303 to the
Refererwhen it is a page of this origin, otherwise to the app's root. A thrownredirect()goes to its target (a 302 becomes 303, the other statuses stay). An error answerstext/plainwith its status and public message; validation issues are not included. - With
security.cookieSecretsconfigured, the outcome of the post (the value, afail()result, or the error with its issues) is carried to the page the browser is sent back to, in a signed one-time cookie, and that page shows it throughuseSubmission(). Without cookie secrets the answers above are all there is. See Dusk: mutations, forms and results.
The function's endpoint is /_dusk/fn/<id>. Both the server-side function and the browser stub expose it as
.url, the same string, so a page renders the form on the server and the browser keeps it after hydration (see
In the browser). The form posts without any script, which is the point of form: true:
src/routes/pin-form.tsxtsx
import type { JSX } from "@shadow-js/web";
import { pinNote } from "../server/forms";
export default function PinForm(): JSX.Element {
return (
<form method="post" action={pinNote.url}>
<input type="hidden" name="id" value="1" />
<button type="submit">Pin note 1</button>
</form>
);
}