stoneware

Documentation

Server actions

Form handling where CSRF verification is structural, not a decorator.

Any exported HTTP method handler under routes/api/ is a server action. By the time it runs, the framework has already verified the CSRF token.

routes/api/subscribe.tstsx
import type { ActionContext } from "stoneware";

export async function POST({ request }: ActionContext) {
  const form = await request.formData();
  const email = String(form.get("email") ?? "");

  return Response.json({ ok: true });
}

Forms

Use the Form helper instead of a raw form element and the hidden token field is injected for you.

routes/index.tsxtsx
import { Form } from "stoneware";

<Form action="/api/subscribe">
  <input type="email" name="email" required />
  <button type="submit">Subscribe</button>
</Form>;

Verification happens in the request pipeline, before any handler is reached, on every non-GET request. It is not something a route opts into — a raw form does not silently skip protection, it simply fails. The token is checked against a clone of the request, so your handler still receives an unconsumed body.

For an island doing its own fetch(), pass the token in as a prop with csrfToken() and send it in the x-csrf-token header.

Something wrong in the framework itself rather than the page? Open an issue on GitHub.