withastro/astro · error · AstroError
`getActionState()` must be called with a form request.
Error message
`getActionState()` must be called with a form request.
What it means
Thrown by @astrojs/react's `getActionState()` when the incoming request's `Content-Type` is missing or not a form content type (`application/x-www-form-urlencoded` or `multipart/form-data`). State retrieval relies on parsing `FormData`, which only exists for form posts.
Source
Thrown at packages/integrations/react/src/actions.ts:50
// React calls `.bind()` internally to pass the initial state value.
// Calling `.bind()` seems to remove our `$$FORM_ACTION` metadata,
// so we need to define our *own* `.bind()` method to preserve that metadata.
Object.defineProperty(callback, 'bind', {
value: (...args: Parameters<typeof callback>) =>
injectStateIntoFormActionData(callback, ...args),
});
return callback;
}
/**
* Retrieve the state object from your action handler when using `useActionState()`.
* To ensure this state is retrievable, use the {@linkcode withState} helper.
*/
export async function getActionState<T>({ request }: { request: Request }): Promise<T> {
const contentType = request.headers.get('Content-Type');
if (!contentType || !isFormRequest(contentType)) {
throw new AstroError(
'`getActionState()` must be called with a form request.',
"Ensure your action uses the `accept: 'form'` option.",
);
}
const formData = await request.clone().formData();
const state = formData.get('_astroActionState')?.toString();
if (!state) {
throw new AstroError(
'`getActionState()` could not find a state object.',
'Ensure your action was passed to `useActionState()` with the `withState()` wrapper.',
);
}
return JSON.parse(state) as T;
}
const formContentTypes = ['application/x-www-form-urlencoded', 'multipart/form-data'];
function isFormRequest(contentType: string) {View on GitHub (pinned to d081033d5f)
Solutions
- Define the action with `accept: 'form'` so Astro routes form posts to it.
- Ensure the React client submits via a `<form action={withState(action)}>` or `useActionState`, which always posts form data.
- Guard `getActionState` behind a content-type check if the action handles multiple accept types.
Example fix
// before
export const action = defineAction({ handler: async ({ request }) => {
const state = await getActionState({ request }); // throws for JSON
}});
// after
export const action = defineAction({
accept: 'form',
handler: async ({ request }) => {
const state = await getActionState({ request });
},
}); Defensive patterns
Strategy: type-guard
Validate before calling
function isFormRequest(request: Request): boolean {
const ct = request.headers.get('Content-Type') ?? '';
const base = ct.split(';')[0].toLowerCase();
return base === 'application/x-www-form-urlencoded' || base === 'multipart/form-data';
}
if (!isFormRequest(request)) { /* respond 415 or skip getActionState */ } Type guard
function isFormContentType(request: Request): request is Request & { clone(): Promise<Request & { formData(): Promise<FormData> }> } {
const ct = (request.headers.get('Content-Type') ?? '').split(';')[0].toLowerCase();
return ct === 'application/x-www-form-urlencoded' || ct === 'multipart/form-data';
} Try / catch
try { await getActionState({ request }); } catch (e) { if (/must be called with a form request/) respondUnsupportedMedia(); else throw e; } Prevention
- Always declare `accept: 'form'` on actions that call `getActionState`.
- Gate state retrieval on a content-type check before calling the API.
- Keep a single code path (React form submission) for stateful actions.
When it happens
Trigger: Calling `getActionState({ request })` inside an action whose `accept` option is `'json'` or omitted while the client sends JSON. A fetch to the action endpoint with `Content-Type: application/json`. A GET request (no body/content-type).
Common situations: Forgetting to set `accept: 'form'` on an action that calls `getActionState`. Mixing JSON-based actions with React `useActionState` flow. A non-React client calling the action endpoint.
Related errors
- `getActionState()` could not find a state object.
- Unsupported content type
- [astro:actions] `defineAction()` unexpectedly used on the cl
- [astro:actions] `getActionContext()` unexpectedly used on th
- BAD_REQUEST
AI-assisted analysis of withastro/astro@d081033d5f (2026-08-12).
Data as JSON: /api/errors/c5a4275b86d4e22e.
Report an issue: GitHub.