{"record":{"id":"384b8d65a602bc7a","repo":"withastro/astro","slug":"unsupported-content-type","errorCode":null,"errorMessage":"Unsupported content type","messagePattern":"Unsupported content type","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"packages/astro/src/actions/runtime/server.ts","lineNumber":303,"sourceCode":"\t\tif (hasContentType(contentType, ['application/json'])) {\n\t\t\tif (contentLength === 0) return undefined;\n\t\t\tif (!hasContentLength) {\n\t\t\t\tconst body = await readBodyWithLimit(request.clone(), bodySizeLimit);\n\t\t\t\tif (body.byteLength === 0) return undefined;\n\t\t\t\treturn JSON.parse(new TextDecoder().decode(body));\n\t\t\t}\n\t\t\treturn await request.clone().json();\n\t\t}\n\t} catch (e) {\n\t\tif (e instanceof BodySizeLimitError) {\n\t\t\tthrow new ActionError({\n\t\t\t\tcode: 'CONTENT_TOO_LARGE',\n\t\t\t\tmessage: `Request body exceeds ${bodySizeLimit} bytes`,\n\t\t\t});\n\t\t}\n\t\tthrow e;\n\t}\n\tthrow new TypeError('Unsupported content type');\n}\n\nexport const ACTION_API_CONTEXT_SYMBOL = Symbol.for('astro.actionAPIContext');\n\nconst formContentTypes = ['application/x-www-form-urlencoded', 'multipart/form-data'];\n\nfunction hasContentType(contentType: string, expected: string[]) {\n\t// Split off parameters like charset or boundary\n\t// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type#content-type_in_html_forms\n\tconst type = contentType.split(';')[0].toLowerCase();\n\n\treturn expected.some((t) => type === t);\n}\n\nfunction isActionAPIContext(ctx: ActionAPIContext): boolean {\n\tconst symbol = Reflect.get(ctx, ACTION_API_CONTEXT_SYMBOL);\n\treturn symbol === true;\n}","sourceCodeStart":285,"sourceCodeEnd":321,"githubUrl":"https://github.com/withastro/astro/blob/52e6c34790cc8ac4e69e6135ace06049867e5c4a/packages/astro/src/actions/runtime/server.ts#L285-L321","documentation":"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.","triggerScenarios":"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'.","commonSituations":"Hand-rolled action fetches copying generic fetch snippets; form-encoding libraries sending unusual MIME types; API gateways normalizing headers and breaking the content type.","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"],"exampleFix":"// before\nawait fetch(actionPath, {\n  method: 'POST',\n  headers: { 'Content-Type': 'text/plain' },\n  body: JSON.stringify(input),\n});\n\n// after\nawait fetch(actionPath, {\n  method: 'POST',\n  headers: { 'Content-Type': 'application/json' },\n  body: JSON.stringify(input),\n});","handlingStrategy":"validation","validationCode":"// set the header explicitly on every custom action fetch\nawait fetch(actionUrl, {\n  method: 'POST',\n  headers: { 'Content-Type': 'application/json' },\n  body: JSON.stringify(input),\n});","typeGuard":"function isSupportedActionContentType(ct: string | null): boolean {\n  if (!ct) return false;\n  const base = ct.split(';')[0].trim().toLowerCase();\n  return ['application/json', 'application/x-www-form-urlencoded', 'multipart/form-data'].includes(base);\n}","tryCatchPattern":"try {\n  await handleActionRequest(request);\n} catch (e) {\n  if (e instanceof TypeError && e.message === 'Unsupported content type') {\n    return new Response('Unsupported media type', { status: 415 });\n  }\n  throw e;\n}","preventionTips":["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"],"tags":["astro-actions","content-type","http","typeerror","fetch"],"backgroundTag":"unsupported-media-type","analyzedSha":"52e6c34790cc8ac4e69e6135ace06049867e5c4a","analyzedAt":"2026-08-18T18:48:03.901Z","contentChangedAt":"2026-08-18T18:48:03.901Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}