vercel/next.js · error · ApiError
Invalid JSON
Error message
Invalid JSON
What it means
Thrown as an ApiError(400) by parseJson() when the request body has Content-Type application/json (or application/ld+json) but the body string is not valid JSON. This becomes a 400 Bad Request response to the client. The body parser special-cases empty bodies (returns {}), so only non-empty malformed JSON triggers this.
Source
Thrown at packages/next/src/server/api-utils/node/parse-body.ts:21
import { parse } from 'next/dist/compiled/content-type'
import isError from '../../../lib/is-error'
import type { SizeLimit } from '../../../types'
import { ApiError } from '../index'
/**
* Parse `JSON` and handles invalid `JSON` strings
* @param str `JSON` string
*/
function parseJson(str: string): object {
if (str.length === 0) {
// special-case empty json body, as it's a common client-side mistake
return {}
}
try {
return JSON.parse(str)
} catch (e) {
throw new ApiError(400, 'Invalid JSON')
}
}
/**
* Parse incoming message like `json` or `urlencoded`
* @param req request object
*/
export async function parseBody(
req: IncomingMessage,
limit: SizeLimit
): Promise<any> {
let contentType
try {
contentType = parse(req.headers['content-type'] || 'text/plain')
} catch {
contentType = parse('text/plain')
}
const { type, parameters } = contentTypeView on GitHub (pinned to 0ae8c72462)
Solutions
- On the client, ensure the body is produced via JSON.stringify(obj) and Content-Type is application/json.
- In the API route, catch the 400 and return a helpful error message guiding the client.
- Validate/normalize the Content-Type; if the client isn't sending JSON, switch to the correct content type.
Example fix
// before (client) - malformed body
fetch('/api/save', { method: 'POST', headers: { 'content-type': 'application/json' }, body: "{name:'bad'" })
// after
fetch('/api/save', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ name: 'good' }) }) Defensive patterns
Strategy: try-catch
Validate before calling
function tryParseJson(body: string): object {
try { return JSON.parse(body) }
catch { throw new Error('Request body is not valid JSON') }
} Try / catch
// In the API route handler
try {
// body is already parsed by Next.js; this pattern applies if you parse manually
} catch (err) {
if (err.message === 'Invalid JSON') res.status(400).json({ error: 'Malformed JSON body' })
} Prevention
- On the client, always use JSON.stringify(obj) and set Content-Type: application/json.
- Return a clear 400 message guiding clients on the expected format.
- Validate Content-Type matches the actual payload.
When it happens
Trigger: A POST/PUT/PATCH to an API route with Content-Type: application/json where the raw body fails JSON.parse — e.g. trailing comma, unquoted keys, truncated payload, or text that isn't JSON at all.
Common situations: Client sends malformed JSON (typo, truncated by a proxy, wrong content-type header on a form payload); a fetch call with a stringified-but-broken body; curl sending raw text with a JSON content type.
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- Body exceeded ${limit} limit
- Could not parsetsconfig.json. Please make sure it contains s
- Could not parse output from TypeScript's --showConfig.
- Invalid ${options.type} header
- Invalid redirect arguments. Please use a single argument URL
AI-assisted analysis of vercel/next.js@0ae8c72462 (2026-08-06).
Data as JSON: /api/errors/9470f77f7dc5be34.
Report an issue: GitHub.