Unexpected token in JSON at position 0 means the first character is not JSON. Diagnose HTML, empty body, double parse, JS literals, BOM, or JSONL, then inspect the raw body in a browser formatter.
Unexpected token in JSON at position 0 does not mean JSON.parse is broken. The parser hit an illegal character at index 0. Chrome currently says Unexpected token '<', "<html>... is not valid JSON"; Firefox says unexpected character at line 1 column 1. Print that character before you touch business logic.
Paste the raw body into the JSON formatter. If you need a payload that actually parses, export one from the test data generator.
Position 0 means the first character is already illegal. Position 17 means the prefix was fine until that offset, often a trailing comma. Look at the character there.
Before JSON.parse or response.json(), log HTTP status, Content-Type, and the first 80 characters of the body.
const raw = await response.text()
console.log(response.status, response.headers.get('content-type'), raw.slice(0, 80))
if (!response.ok) throw new Error(raw.slice(0, 120))
JSON.parse(raw)
Starts with Likely cause
< HTML; stop parsing
empty 204 or empty body
{ / [ JSON, or a syntax error later
[object Object] Already an object
fetch's response.json() often hides the raw HTML. Read text() and parse yourself.
Index 0 is < because the body is <!DOCTYPE html>, <html>, or a login page. A wrong URL can still return index.html with HTTP 200. Unauthenticated calls may 302 to a login page. 5xx HTML, the default Nginx page, and a proxy aimed at a static host do the same.
JSON.parse('<!DOCTYPE html><html><body>Not found</body></html>')
// Unexpected token '<', "<!DOCTYPE "... is not valid JSON
In Network, open Response, not Preview. Check the final URL and status. If response.ok is false, read text() first. Server errors should be JSON too, such as {"error":"not_found"} with Content-Type: application/json. You cannot repair a web page into an API payload.
V8 / Chrome reports Unexpected end of JSON input for JSON.parse(''). The parser hits end of string at position 0.
JSON.parse('')
// Unexpected end of JSON input
Typical sources: 204 No Content, DELETE or logout with no object, a gateway timeout that yields ''. JSON.parse(null) returns null in JavaScript (it treats null as JSON null) and does not throw. JSON.parse(undefined) does throw, because it becomes the string "undefined".
Treat an empty body as null or {}. Do not JSON.parse(''). If the contract needs an object, return {} or [], not 200 plus whitespace.
JSON.parse wants a string. Axios parses JSON when Content-Type says so. A second JSON.parse(data) stringifies the object to "[object Object]" first. Current Node / Chrome: "[object Object]" is not valid JSON.
JSON.parse({ id: 1 })
// "[object Object]" is not valid JSON
Parse only when typeof value === 'string'. If you see [object Object], drop the extra parse. On Axios errors, log error.response.data instead of parsing again.
JSON is not an object literal: keys must be double-quoted, strings too, no trailing commas, comments, or undefined. Copy-paste from configs or chat often includes JSONC.
You get position 0 only if the illegal character is first. Unquoted keys usually fail after {:
JSON.parse("'nope'")
// Unexpected token ''', "'nope'" is not valid JSON
JSON.parse("{name: 'Ada'}")
// Expected property name or '}' in JSON at position 1
Use the JSON formatter for line and column. Trailing commas and single quotes can be repaired as syntax only. Do not emit trailing commas from production APIs. If you need comments, use a JSONC parser or YAML. Do not eval API bodies.
After JSON.stringify(JSON.stringify(obj)), the first parse still returns a string.
const twice = JSON.stringify(JSON.stringify({ ok: true }))
typeof JSON.parse(twice) // 'string'
JSON.parse(JSON.parse(twice)) // { ok: true }
Check typeof. If it is still a string, parse once more or drop the extra stringify upstream. Do not loop without a bound.
A UTF-8 BOM (EF BB BF, character U+FEFF) sits at the start of some files. Word, Excel, and some Windows exports add it. Current Node:
JSON.parse('\uFEFF{"ok":true}')
// Unexpected token '', "{"ok":true}" is not valid JSON
Log prefixes fail the same way: undefined{"id":1} has token u. Save UTF-8. Do not print logs in front of a JSON body. To strip a BOM: text.charCodeAt(0) === 0xfeff ? text.slice(1) : text.
Timeouts, proxy buffers, or a copied file missing } give a partial string. Two JSON values stuck together also fail:
JSON.parse('{"a":1}{"b":2}')
// Unexpected non-whitespace character after JSON at position 7
Log files are often JSON Lines: one object per line. JSON.parse on the whole file fails. Trim and parse each line. Repeated json.dump appends in a loop produce invalid JSON. Write one array, or use .jsonl.
HTML error pages belong to the URL, auth, and gateway. The JSON formatter only flags syntax. Paste the Network Response text, not Preview after it has already become an object.
Related: JSON formatter · Blog index