trpc/trpc · error · TRPCError
UNSUPPORTED_MEDIA_TYPE
UNSUPPORTED_MEDIA_TYPE
Error message
Unsupported content-type "${req.headers.get('content-type')} What it means
`getContentTypeHandler` matches the request against registered handlers (JSON, formData, octet-stream). If none match and the method is not GET (GET falls back to JSON so endpoints open in a browser), the request is rejected as UNSUPPORTED_MEDIA_TYPE, echoing the offending content-type.
Source
Thrown at packages/server/src/unstable-core-do-not-import/http/contentType.ts:307
const handlers = [
jsonContentTypeHandler,
formDataContentTypeHandler,
octetStreamContentTypeHandler,
];
function getContentTypeHandler(req: Request): ContentTypeHandler {
const handler = handlers.find((handler) => handler.isMatch(req));
if (handler) {
return handler;
}
if (!handler && req.method === 'GET') {
// fallback to JSON for get requests so GET-requests can be opened in browser easily
return jsonContentTypeHandler;
}
throw new TRPCError({
code: 'UNSUPPORTED_MEDIA_TYPE',
message: req.headers.has('content-type')
? `Unsupported content-type "${req.headers.get('content-type')}`
: 'Missing content-type header',
});
}
export async function getRequestInfo(
opts: GetRequestInfoOptions,
): Promise<TRPCRequestInfo> {
const handler = getContentTypeHandler(opts.req);
return await handler.parse(opts);
}
View on GitHub (pinned to acff82332d)
Solutions
- Send `application/json` (or one of the supported content-types) from the client.
- Ensure client and server versions agree on the supported content-type set.
- For raw uploads use `application/octet-stream` with POST; for form uploads use `multipart/form-data` with POST.
Example fix
// before
fetch('/api/trpc/user.get', {
method: 'POST',
headers: { 'content-type': 'application/x-www-form-urlencoded' },
body: 'id=1',
})
// after
fetch('/api/trpc/user.get', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ id: 1 }),
}) Defensive patterns
Strategy: validation
Validate before calling
// Validate content-type against the supported set
const SUPPORTED = ['application/json', 'multipart/form-data', 'application/octet-stream'];
function assertSupportedContentType(ct: string | null): void {
if (!ct || !SUPPORTED.some((s) => ct.startsWith(s))) {
throw new Error(`Unsupported content-type: ${ct}`);
}
} Type guard
function isSupportedContentType(ct: string | null): boolean {
return !!ct && ['application/json', 'multipart/form-data', 'application/octet-stream']
.some((s) => ct.startsWith(s));
} Try / catch
try {
await trpc.proc.mutate(body);
} catch (e) {
if (e instanceof TRPCError && e.code === 'UNSUPPORTED_MEDIA_TYPE') {
// Switch the request to application/json (or another supported type)
}
throw e;
} Prevention
- Send one of the supported content-types from the client.
- Keep client and server versions aligned so the supported set matches.
- For raw uploads use octet-stream with POST; for form uploads use multipart/form-data with POST.
When it happens
Trigger: Sending an unsupported type such as `text/xml` or `application/x-www-form-urlencoded` with a non-GET method; a content-type with parameters that defeat the `startsWith` matcher.
Common situations: Wrong serializer on the client; a client defaulting to form-urlencoded; a version mismatch where a handler was removed or renamed.
Related errors
- METHOD_NOT_SUPPORTED
- METHOD_NOT_SUPPORTED
- FormData is only supported for mutations
- Octet type input is only supported for mutations
- Subscriptions are unsupported by `httpLink` - use `httpSubsc
AI-assisted analysis of trpc/trpc@acff82332d (2026-08-12).
Data as JSON: /api/errors/4ce953eb3a137286.
Report an issue: GitHub.