withastro/astro · error · TypeError
Unsupported content type
Error message
Unsupported content type
What it means
parseRequestBody only recognizes form content types (application/x-www-form-urlencoded, multipart/form-data) and application/json. Any other Content-Type falls through to throw new TypeError('Unsupported content type').
Source
Thrown at packages/astro/src/actions/runtime/server.ts:302
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 d081033d5f)
Solutions
- Send application/json for JSON actions (let the client proxy set it).
- For form posts, ensure a proper form Content-Type with a valid boundary.
- Verify the request Content-Type header on the client before sending.
Example fix
// before - manual fetch with wrong header
await fetch(actionUrl, { method: 'POST', headers: { 'Content-Type': 'text/plain' }, body: JSON.stringify(data) });
// after
await fetch(actionUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }); Defensive patterns
Strategy: validation
Validate before calling
// Only send recognized content types to action requests.
const ALLOWED = new Set(['application/json', 'application/x-www-form-urlencoded', 'multipart/form-data']);
const ct = headers.get('Content-Type')?.split(';')[0].trim().toLowerCase();
if (!ct || !ALLOWED.has(ct)) throw new Error(`Unsupported Content-Type: ${ct}`); Type guard
function isSupportedContentType(ct: string): boolean {
const base = ct.split(';')[0].trim().toLowerCase();
return ['application/json', 'application/x-www-form-urlencoded', 'multipart/form-data'].includes(base);
} Try / catch
try {
await actions.myAction(payload);
} catch (e) {
if (e instanceof TypeError && /Unsupported content type/i.test(e.message)) {
// set a proper Content-Type and retry
} else throw e;
} Prevention
- Let the actions client proxy set Content-Type instead of setting it manually.
- Use application/json for JSON actions and real form types for form actions.
- Validate the Content-Type header before custom fetches.
When it happens
Trigger: An action request whose Content-Type is text/plain, application/xml, a malformed multipart without a valid boundary, or any unrecognized type.
Common situations: A custom fetch that forgets to set Content-Type; a client sending text/plain; a proxy rewriting the header; a corrupted multipart boundary.
Related errors
- `getActionState()` must be called with a form request.
- [astro:actions] `defineAction()` unexpectedly used on the cl
- [astro:actions] `getActionContext()` unexpectedly used on th
- BAD_REQUEST
- ActionCalledFromServerError
AI-assisted analysis of withastro/astro@d081033d5f (2026-08-12).
Data as JSON: /api/errors/384b8d65a602bc7a.
Report an issue: GitHub.