withastro/astro · error · TypeError
Unsupported content type
Error message
Unsupported content type
What it means
parseRequestBody only understands the two content shapes actions support: form types (application/x-www-form-urlencoded, multipart/form-data) and application/json. If the request declares any other content-type (text/plain, application/xml, octet-stream...), parsing is skipped and this TypeError is thrown — Astro refuses to guess how to interpret the body.
Solutions
- Send the correct header: Content-Type: application/json for object payloads, or let the browser set form types when submitting FormData
- Remove explicit content-type overrides so fetch infers it from the body type
- Check intermediate proxies/middleware for header rewrites on _action requests
Example fix
// before
await fetch(actionPath, {
method: 'POST',
headers: { 'Content-Type': 'text/plain' },
body: JSON.stringify(input),
});
// after
await fetch(actionPath, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(input),
}); Defensive patterns
Strategy: validation
Validate before calling
// set the header explicitly on every custom action fetch
await fetch(actionUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(input),
}); Type guard
function isSupportedActionContentType(ct: string | null): boolean {
if (!ct) return false;
const base = ct.split(';')[0].trim().toLowerCase();
return ['application/json', 'application/x-www-form-urlencoded', 'multipart/form-data'].includes(base);
} Try / catch
try {
await handleActionRequest(request);
} catch (e) {
if (e instanceof TypeError && e.message === 'Unsupported content type') {
return new Response('Unsupported media type', { status: 415 });
}
throw e;
} Prevention
- Prefer the astro:actions client proxy over hand-rolled fetch — it sets correct headers
- When using fetch directly, always set Content-Type: application/json
- Audit middleware/proxies for header rewrites on action routes
When it happens
Trigger: Calling an action endpoint with fetch(..., { headers: { 'Content-Type': 'text/plain' } }); custom clients defaulting to other MIME types; middleware or proxies rewriting the content-type header; curl posts without -H 'Content-Type: application/json'.
Common situations: Hand-rolled action fetches copying generic fetch snippets; form-encoding libraries sending unusual MIME types; API gateways normalizing headers and breaking the content type.
Related errors
- Expected handler for action
- Expected `server` export in actions file to be an object…
- ActionCalledFromServerError
- ActionCalledFromServerError
- ActionNotFoundError
AI-assisted analysis of withastro/astro@52e6c34790 (2026-08-18).
Data as JSON: /api/errors/384b8d65a602bc7a.
Report an issue: GitHub.
Appendix: source
Thrown at packages/astro/src/actions/runtime/server.ts:303
if (hasContentType(contentType, ['application/json'])) {
if (contentLength === 0) return undefined;
if (!hasContentLength) {
const body = await readBodyWithLimit(request.clone(), bodySizeLimit);
if (body.byteLength === 0) return undefined;
return JSON.parse(new TextDecoder().decode(body));
}
return await request.clone().json();
}
} catch (e) {
if (e instanceof BodySizeLimitError) {
throw new ActionError({
code: 'CONTENT_TOO_LARGE',
message: `Request body exceeds ${bodySizeLimit} bytes`,
});
}
throw e;
}
throw new TypeError('Unsupported content type');
}
export const ACTION_API_CONTEXT_SYMBOL = Symbol.for('astro.actionAPIContext');
const formContentTypes = ['application/x-www-form-urlencoded', 'multipart/form-data'];
function hasContentType(contentType: string, expected: string[]) {
// Split off parameters like charset or boundary
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type#content-type_in_html_forms
const type = contentType.split(';')[0].toLowerCase();
return expected.some((t) => type === t);
}
function isActionAPIContext(ctx: ActionAPIContext): boolean {
const symbol = Reflect.get(ctx, ACTION_API_CONTEXT_SYMBOL);
return symbol === true;
}View on GitHub (pinned to 52e6c34790)