Unexpected Token in JSON: What It Means and How to Fix It
This error means the text you tried to parse is not JSON. The token it names usually tells you exactly what you received instead.
You call JSON.parse() or response.json() and get a SyntaxError instead of data. The wording depends on the browser, but the meaning is always the same: the parser read the text from the start and hit a character that cannot appear there in valid JSON. The character it quotes, and the position, are the most useful clues you have.
The same error in different engines
Newer versions of Chrome and Node.js changed the wording, so you will find both versions in search results and in older logs.
| Where you see it | Message |
|---|---|
| Chrome, Edge, Node.js 20 and later | Unexpected token '<', "<!DOCTYPE "... is not valid JSON |
| Older Chrome and Node.js 18 | Unexpected token < in JSON at position 0 |
| Firefox | JSON.parse: unexpected character at line 1 column 1 of the JSON data |
| Safari | JSON Parse error: Unrecognized token '<' |
"Position 0" or "line 1 column 1" means the very first character was already wrong. That almost always points to the response being something other than JSON, rather than a small typo inside a JSON document.
The usual cause: an HTML page instead of JSON
An unexpected < at position 0 is by far the most common version. The code asked an API for JSON, but the server answered with an HTML page, and HTML starts with <!DOCTYPE html> or <html>. Typical reasons:
- A wrong URL: a typo or a missing
/apiprefix returns the site's 404 page. - A server error: the framework renders an HTML 500 error page.
- An expired session: the request is redirected to a login page, and
fetchfollows the redirect silently. - A single-page app fallback: the dev server or hosting returns
index.htmlfor any unknown path, with status 200. - A proxy, CDN or firewall in between sends its own HTML error or challenge page.
The quickest way to confirm it is to look at the raw response before parsing it:
const res = await fetch('/api/items');
console.log(res.status, res.headers.get('content-type'));
const text = await res.text();
console.log(text.slice(0, 200)); // shows the HTML if that is what came back
In the browser you can also open the Network panel, click the request and check the Response tab. If you see HTML, fix the URL, the authentication or the server error; the parsing code itself is fine.
Other tokens and what they point to
| Current message (older message) | What you parsed | Fix |
|---|---|---|
"undefined" is not valid JSON (Unexpected token u in JSON at position 0) | The value undefined, for example a missing localStorage key or an unset variable | Check that the value exists before parsing it |
"[object Object]" is not valid JSON (Unexpected token o in JSON at position 1) | An object that was already parsed, turned into a string by JSON.parse | Use the object directly; do not parse it twice |
Expected property name or '}' in JSON at position 1 | Single quotes or unquoted keys, as in {'a': 1} or {a: 1} | Use double quotes around every key and string |
Expected double-quoted property name in JSON at position 8 | A trailing comma, as in {"a": 1,} | Remove the comma before the closing brace |
Unexpected token 'N', "{"a": NaN}" is not valid JSON | NaN, Infinity or undefined inside the data | Replace them with null or a number |
| A message that quotes an invisible or empty-looking token at position 0 | A byte order mark at the start of a file saved by some Windows editors | Save the file as UTF-8 without BOM, or strip the first character |
Parsing data that is already parsed
Libraries often hand you ready-made objects. response.json() already returns a JavaScript object, and so does axios in response.data. Passing that object to JSON.parse converts it to the string "[object Object]" first, which fails at position 1. Remove the extra parse:
const data = await res.json();
// JSON.parse(data) <- fails: data is already an object
console.log(data.items);
Finding a mistake inside real JSON
When the error position is somewhere in the middle, the text really is JSON with a mistake in it. Paste it into the JSON Validator: it uses the same parser as your browser, shows the message with its position and line, and formats the document as soon as it is valid. The actual mistake is usually just before the reported position, such as a missing comma between two items or a quote that should have been a double quote.
Guarding against it in your code
Checking the status and content type before parsing turns a confusing syntax error into a message that says what really happened:
async function getJson(url) {
const res = await fetch(url);
const type = res.headers.get('content-type') || '';
if (!res.ok || !type.includes('application/json')) {
const body = await res.text();
throw new Error(`Expected JSON from ${url}, got ${res.status} ${type}: ${body.slice(0, 100)}`);
}
return res.json();
}
The same idea applies on the server side: return JSON error bodies from API routes instead of HTML error pages, and send Content-Type: application/json with them. For the closely related message you get when the body is empty or cut off, see Unexpected end of JSON input.