Fix "Unexpected token in JSON" Errors
"Unexpected token in JSON" means JSON.parse got text that isn't JSON. Read the exact message, log the raw text, and match it to one of five causes. Most of the time it's an HTML error or login page.
"Unexpected token in JSON" means JSON.parse (or response.json()) was given text that isn't valid JSON. The bug is almost never in JSON.parse. It's in the text you fed it. So the fix always starts the same way: look at the actual text. The message usually tells you what kind of text it was.
Skip ahead: Getting Unexpected token '<'? Skip to "Cause 1: you got HTML".
Step 1: Read the exact message
Modern Chrome and Node (V8) quote the start of the bad text. These are the real messages from Node 24:
const samples = [
"<!DOCTYPE html><html>",
"",
"undefined",
"{name: \"Ana\"}",
"{\"a\": 1,}",
"{\"total\": 1500",
"OK",
];
for (const text of samples) {
try {
JSON.parse(text);
} catch (error) {
console.log(`${error.name}: ${error.message}`);
}
}
SyntaxError: Unexpected token '<', "<!DOCTYPE "... is not valid JSON
SyntaxError: Unexpected end of JSON input
SyntaxError: "undefined" is not valid JSON
SyntaxError: Expected property name or '}' in JSON at position 1 (line 1 column 2)
SyntaxError: Expected double-quoted property name in JSON at position 8 (line 1 column 9)
SyntaxError: Expected ',' or '}' after property value in JSON at position 14 (line 1 column 15)
SyntaxError: Unexpected token 'O', "OK" is not valid JSON
Firefox and Safari word them differently (Firefox says JSON.parse: unexpected character at line 1 column 1 of the JSON data), but the causes are the same.
Step 2: Log the raw text
If the text comes from fetch, don't call response.json() while debugging. Read it as text and look:
const response = await fetch(url);
const text = await response.text();
console.log(response.status, response.headers.get("content-type"));
console.log(text.slice(0, 300));
const data = JSON.parse(text);
In the browser, the Network tab shows the same thing: click the request, then Response. You now know what you got, and the causes below are easy to match.
Cause 1: you got HTML
Unexpected token '<', "<!DOCTYPE ".... The server sent a web page, not JSON. Usual reasons, most likely first:
- Wrong URL. A typo, a missing
/apiprefix, or a relative URL that resolved against the current page. Many servers and single-page apps return their HTML page for any unknown path. - An error page. A 404, 500 or 502 page from the server or proxy. Check
response.status. - A login page. The session expired and the server redirected to its login form.
fetchfollows redirects, so you see a 200 with HTML.
The fix is to check before parsing:
async function getJson(url) {
const response = await fetch(url);
const type = response.headers.get("content-type") ?? "";
if (!response.ok) {
throw new Error(`HTTP ${response.status} from ${url}`);
}
if (!type.includes("application/json")) {
throw new Error(`Expected JSON from ${url}, got ${type || "no content-type"}`);
}
return response.json();
}
try {
await getJson("https://jsonplaceholder.typicode.com/users/999");
} catch (error) {
console.log(error.message);
}
const user = await getJson("https://jsonplaceholder.typicode.com/users/1");
console.log(user.name);
HTTP 404 from https://jsonplaceholder.typicode.com/users/999
Leanne Graham
Now the error says what actually went wrong, instead of complaining about a <.
Cause 2: the text is empty
Unexpected end of JSON input. Common with 204 No Content responses, a DELETE that returns nothing, or a server that crashed mid-response. Check before parsing:
const text = await response.text();
const data = text ? JSON.parse(text) : null;
Cause 3: it was undefined
"undefined" is not valid JSON. You passed a variable that was never set. Typical: JSON.parse(response.data) when the field is called something else, or reading a property that doesn't exist. Log the variable right before the parse. Note that a missing localStorage key gives null, and JSON.parse(null) returns null without throwing, so that one fails later, somewhere else.
Cause 4: it's JavaScript, not JSON
Expected property name or '}' and Expected double-quoted property name mean someone wrote JSON by hand: keys without quotes, single quotes, a trailing comma, or a comment. JSON is stricter than JavaScript. If it's a file you control, fix the file, and paste it into a validator or run node -e "JSON.parse(require('fs').readFileSync('data.json','utf8'))" to find the position. If you build JSON in code, never glue strings together. Use JSON.stringify(data).
Cause 5: it was already parsed
"[object Object]" is not valid JSON. You called JSON.parse on something that's already an object, often because a library like axios already parsed the response for you. Remove the extra parse.
⚠ Warning: Don't wrap the parse in try/catch and return an empty object to make the error go away. The page will show nothing, no error will be logged, and you'll lose the one clue you had. Catch it, log the status and the first part of the text, and show the user a real "couldn't load" message.
💡 Tip: When a JSON error only happens in production, it's almost always Cause 1. Production has a proxy, a login wall or a CDN error page that your local setup doesn't. Log response.status and content-type there and you'll see it in one try.
Next time you see this error, what's the first thing you'll log: the parsed data, or the raw text?
About
Read the real V8 error messages, log the raw response, and fix the five usual causes, from HTML error pages to text that was already parsed. After this, you find the cause in one try instead of guessing.
- Responsible
- John G
- Last Update
- 09/05/2026
- Reading Time
- 5 min
Reviews
Sign in to write a reviewComments (0)
Sign in to commentNo comments yet.