You're staring at a red stack trace: SyntaxError: Unexpected token '<', "..." is not valid JSON. Or maybe it's Unexpected token u in JSON at position 0. Either way, JSON.parse() refused to run and your code stopped working. This guide explains exactly what each variant of this error means, why it happens, and how to fix it — fast.
What the Error Actually Looks Like
The wording varies by JavaScript engine, but they all mean the same thing: the parser hit a character it didn't expect at a specific position in your string.
// V8 (Chrome, Node.js, Edge)
SyntaxError: Unexpected token '<', "<html>..." is not valid JSON
SyntaxError: Unexpected token 'u', "undefined" is not valid JSON
SyntaxError: Unexpected token u in JSON at position 0 // older V8
SyntaxError: Expected ',' or '}' after property value in JSON at position 42
// Firefox (SpiderMonkey)
SyntaxError: JSON.parse: unexpected character at line 1 column 1 of the JSON data
SyntaxError: JSON.parse: unexpected non-whitespace character after JSON data
// Safari (JavaScriptCore)
SyntaxError: JSON Parse error: Unexpected identifier "undefined"
SyntaxError: JSON Parse error: Single quotes (') are not allowed in JSONThe key information is always in two parts: what character was unexpected and where. The character tells you what the parser actually received; the position tells you where in the string to look.
Cause 1: The Server Returned HTML Instead of JSON
This is the most common cause of Unexpected token '<'. Your fetch or AJAX call got back an HTML page — usually a 404 page, a login redirect, or a server error — instead of the JSON you expected. The < is the opening <!DOCTYPE html> or <html> tag.
// ❌ This crashes with "Unexpected token '<'"
const res = await fetch('/api/user');
const data = await res.json(); // server returned HTML 404
// ✅ Check the response status first
const res = await fetch('/api/user');
if (!res.ok) {
throw new Error(`HTTP ${res.status}: ${res.statusText}`);
}
const data = await res.json(); Always check res.ok (or res.status) before calling res.json(). You can also inspect the raw response text first:
const text = await res.text();
console.log(text.slice(0, 200)); // see what you actually got
const data = JSON.parse(text);Cause 2: The Value Is undefined
Unexpected token 'u' at position 0 almost always means you called JSON.parse(undefined). The string "undefined" starts with the letter u, which is not a valid JSON start character.
// ❌ variable was never set
const raw = localStorage.getItem('settings'); // returns null if missing
JSON.parse(raw); // null is fine, but if raw is undefined somehow...
// ❌ async data not yet loaded
JSON.parse(this.state.data); // data is undefined on first render
// ✅ Guard before parsing
if (raw) {
const data = JSON.parse(raw);
} Note that localStorage.getItem() returns null (not undefined) for missing keys, and JSON.parse(null) returns null without error. The usual culprits are uninitialized state variables or a missing function argument.
Cause 3: Single Quotes Instead of Double Quotes
// ❌ SyntaxError: Unexpected token '''
JSON.parse("{'name': 'Ada'}");
// ✅ JSON requires double quotes
JSON.parse('{"name": "Ada"}')JavaScript object literals accept single quotes. JSON does not — all strings, including keys, must use double quotes. This often happens when someone serializes data with Python's str() function instead of json.dumps(), or copies an object literal directly.
Cause 4: Trailing Comma
// ❌ SyntaxError: Unexpected token '}'
JSON.parse('{"name": "Ada", "score": 98,}');
// ^ trailing comma
// ✅
JSON.parse('{"name": "Ada", "score": 98}')Modern JavaScript allows trailing commas in arrays and objects. JSON does not. Hand-edited JSON config files are the most common source of this error.
Cause 5: Unquoted Keys
// ❌ SyntaxError: Unexpected token 'n'
JSON.parse("{name: 'Ada'}");
// ✅
JSON.parse('{"name": "Ada"}') Object keys in JSON must always be double-quoted strings. Bare identifiers like name are valid in JavaScript object literals but not in JSON.
Cause 6: Comments in the JSON
// ❌ SyntaxError: Unexpected token '/'
JSON.parse(`{
// user record
"name": "Ada"
}`);
// ✅ Strip comments before parsing (or use a JSONC parser)
const stripped = raw.replace(///.*$/gm, '').replace(//*[sS]*?*//g, '');
JSON.parse(stripped);JSON has no comment syntax. Comments are frequently added to config files (the JSONC format), but the standard JSON.parse() will reject them immediately.
Cause 7: A BOM or Invisible Character at Position 0
If the error says position 0 but the first visible character looks fine, there may be an invisible byte-order mark (BOM, U+FEFF) or other control character prepended to the string — typically when reading a file saved with UTF-8-BOM encoding on Windows.
// ✅ Strip BOM before parsing
const clean = raw.replace(/^/, '');
JSON.parse(clean);Cause 8: Tokens I or N — Infinity and NaN
If the error says Unexpected token I or Unexpected token N, the source produced Infinity, -Infinity, or NaN — all valid in JavaScript but not in JSON.
// ❌ Producer-side mistake
const json = '{"ratio": NaN, "limit": Infinity}';
JSON.parse(json); // Unexpected token N (or I) ...
// ✅ Encode them deliberately
const safe = JSON.stringify({ ratio: NaN, limit: Infinity });
// → '{"ratio":null,"limit":null}' (note: lossy — both become null)
// ✅ Lossless: encode as a string sentinel you parse back yourself
JSON.stringify({ ratio: 'NaN', limit: 'Infinity' });JSON.stringify silently converts NaN / Infinity to null. If you need them to round-trip, replace them with a sentinel string on the way out and parse it back on the way in.
How to Find the Exact Position of the Error
The error message includes a position number. Use it to slice the string and see exactly what's there:
function parseWithContext(text) {
try {
return JSON.parse(text);
} catch (err) {
// Extract position from V8 error message
const match = err.message.match(/position (\d+)/);
if (match) {
const pos = Number(match[1]);
const snippet = text.slice(Math.max(0, pos - 20), pos + 20);
console.error(`Error near: ..."${snippet}"...`);
console.error(` ${''.padStart(Math.min(pos, 20), ' ')}^`);
}
throw err;
}
}Frequently Asked Questions
What does "Unexpected token in JSON" mean?
It means JSON.parse() hit a character that isn't valid at that point in the grammar. The named token tells you what was received (< = HTML, u = undefined, ' = single quote) and the position tells you where to look.
Why does "Unexpected token '<'" happen with fetch?
Your request returned an HTML page — a 404, a login redirect, or a server error — instead of JSON, and the < is the opening HTML tag. Check response.ok before calling response.json() and log await response.text() to see what actually came back.
How do I fix "Unexpected token u in JSON at position 0"?
You passed undefined to JSON.parse(). Guard the value first (if (raw) JSON.parse(raw)) and check for uninitialised state or a missing function argument. The closely related token o variant is covered in Unexpected token o in JSON.
How do I find the exact position of the error?
Read the position number from the error message and slice the string around it (see the snippet above), or paste the JSON into JSON Fix, which highlights the exact offending line.
The Fastest Fix: Paste and Repair Online
If you have a broken JSON string and just need it fixed right now, JSON Fix handles all the causes above automatically — single quotes, trailing commas, unquoted keys, comments, Python literals, and more. Paste your JSON, click Repair & Format, and get clean valid JSON back in one step. Everything runs in your browser; nothing is sent to any server.
- JSON Fix — auto-repair broken JSON and see the exact error line
- Fix JSON Unexpected Token Errors — quick reference for every unexpected token variant
- Fix "[object Object] is Not Valid JSON" — the related error when an object is coerced to a string before parsing
- JSON Diff — compare a before/after JSON to verify your fix didn't change anything unexpected