Unexpected token in JSON: causes and fixes
“Unexpected token” means the parser hit a character that isn’t allowed at that point in JSON. The character quoted in the message is the best clue to what went wrong.
The most common version is Unexpected token '<'. The text you tried to parse starts with an HTML tag, which means the server sent back a web page instead of JSON.
Quick fix
- Look at the response in your browser’s Network tab, or log
await response.text()before parsing. - If it’s HTML, check the status code. A 404 means the URL is wrong. A 401, a 403, or a redirect to a login page means the request wasn’t authenticated.
- If the character is
uoro, check the value you’re passing toJSON.parse. It isn’t a JSON string.
Find and fix it in the validator
Paste your broken JSON into the JSON validator. It underlines the first error as you type, and Fix My JSON repairs common mistakes such as missing quotes and extra commas. It runs in your browser, so your JSON isn’t uploaded.
This example is an HTML 404 page, the kind of response that causes this error. The validator reports the error at the very first character. Fix My JSON can’t turn a web page into JSON, so the fix is in the request that returned it.
<!DOCTYPE html>
<html>
<head><title>404 Not Found</title></head>
<body><h1>Not Found</h1></body>
</html>The message in each browser and language
You might see any of these, depending on where the JSON is parsed:
| Where | Message |
|---|---|
| Chrome, Edge, Node.js 20+ | Unexpected token '<', "<!DOCTYPE "... is not valid JSON |
| Older Chrome, 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 '<' |
| Python | JSONDecodeError: Expecting value: line 1 column 1 (char 0) |
What the unexpected character means
| Character | What was parsed | Usual cause |
|---|---|---|
< | An HTML page instead of JSON | The server sent an error page, a login page, or your app’s index.html. |
u | The text undefined | You passed undefined to JSON.parse, often a property or variable that was never set. |
o | The text [object Object] | An object was turned into a string without JSON.stringify, or you’re parsing data that’s already been parsed. |
N | NaN | JSON has no NaN. Replace it with null or a number before sending. |
' | A single quote | Strings and property names need double quotes. |
(invisible) | A byte order mark | The file was saved as “UTF-8 with BOM”. Save it as plain UTF-8. |
Newer versions of Chrome and Node.js word the u and o cases as "undefined" is not valid JSON and "[object Object]" is not valid JSON. Older versions say Unexpected token u in JSON at position 0 and Unexpected token o in JSON at position 1. For single quotes, see Expecting property name enclosed in double quotes.
Why an API returns HTML instead of JSON
Open your browser’s developer tools, go to the Network tab, select the failing request, and look at its Response. It’s usually one of these:
- A 404 page. The URL is wrong. Check for a missing
/apiprefix or a typo. A relative URL such asfetch("api/users")is resolved from the current page, so on/settings/it requests/settings/api/users. Start the path with/. - Your app’s own index.html. Development servers for single-page apps, such as Vite, return index.html with status 200 for any path they don’t recognize. If the API runs on another port, set up the dev server’s proxy or use the API’s full URL.
- A login page. The session expired or the request didn’t include credentials, and
fetchfollowed the redirect to the sign-in page. - A server or proxy error page. A 500 from your framework, or a 502 or 504 from nginx or a load balancer, often comes back as HTML.
- A CDN or firewall page. Rate-limit, bot-check, and maintenance pages are HTML too.
How to fix it in JavaScript
response.json() parses the body without looking at the status code or content type, so a failed request shows up as a JSON error instead of an HTTP error. Read the body as text, check response.ok and the content type, and then parse it. If something’s wrong, the error you throw includes what the server actually sent:
const response = await fetch("/api/users");
const body = await response.text();
if (!response.ok) {
throw new Error(`Request failed with ${response.status}: ${body.slice(0, 200)}`);
}
if (!response.headers.get("content-type")?.includes("application/json")) {
throw new Error(`Expected JSON, got: ${body.slice(0, 200)}`);
}
const users = JSON.parse(body);For the u and o cases, check what you’re passing to JSON.parse. response.json() and axios’s response.data already return objects, so they don’t need parsing again. When storing an object in localStorage, save JSON.stringify(value), because saving the object directly stores the text [object Object].
In Python, the same HTML response raises a different error. See Expecting value: line 1 column 1.
Still stuck? Paste your JSON into the validator to see exactly where the first error is, or try Fix My JSON.