An unexpected token in JSON error means your parser hit a character it did not expect while reading what was supposed to be valid JSON. It is almost never a mysterious bug. It traces to one of six concrete causes: a trailing comma, single quotes, an HTML response, an invisible BOM, unquoted keys, or comments. This guide walks each one and, more importantly, shows you how to find the exact line and column instead of guessing.
The frustrating part is that the error message rarely points at the real problem. Unexpected token < in JSON at position 0 does not mean your JSON is broken. It means you are not looking at JSON at all. Once you can read what the parser is telling you, most of these fixes take under a minute.
What "Unexpected Token in JSON" Actually Means#
JSON is a strict format. The parser reads your string character by character and builds a value. The moment it sees something that cannot legally appear in that position, it stops and throws a SyntaxError. The "token" is whatever character tripped it up.
Modern engines word the message differently, which adds to the confusion:
- V8 (Chrome, Node 20+):
Unexpected token 'x', "...context..." is not valid JSON - Older Node / Chrome:
Unexpected token x in JSON at position 42 - Firefox:
JSON.parse: unexpected character at line 2 column 5 of the JSON data - Safari:
JSON Parse error: Unexpected identifier
The single most useful field is the position or line/column number. Firefox gives you line and column directly. V8 gives you a byte position and a snippet of surrounding text. Read that first, before you read anything else.
The fastest way to turn a cryptic position into a visible problem is to paste the raw string into a validator that highlights the failing line. Our free JSON formatter and validator marks the exact spot and tells you what it expected there, which collapses most of the debugging below into a glance.
The 6 Real Causes (and How to Fix Each)#
Here is the honest distribution. In real-world debugging, the overwhelming majority of these errors are causes 1 through 3. The last three are rarer but waste hours because they are invisible or counterintuitive.
| Cause | Typical message | Tell |
|---|---|---|
| HTML instead of JSON | Unexpected token < at position 0 | The first char is < (a <!DOCTYPE or <html>) |
| Trailing comma | Unexpected token } or ] | A comma sits before a closing brace or bracket |
| Single quotes | Unexpected token ' | Keys or strings use ' instead of " |
| BOM / hidden chars | Unexpected token at position 0 on valid-looking JSON | Looks perfect but errors at the very start |
| Unquoted keys | Unexpected token near a key | A key like name: has no quotes |
| Comments | Unexpected token / | A // or /* */ comment exists in the file |
Cause 1: You got HTML, not JSON (the "token <" case)#
This is the one that confuses people most, and almost no other guide explains it. If you see Unexpected token < in JSON at position 0, the < is the opening of an HTML tag. Your fetch call expected JSON but the server returned an HTML page.
It usually means one of these:
- The endpoint returned a 404 or 500 error page (HTML), not your API payload.
- You hit the wrong URL and got the app's
index.htmlback. - A proxy, login wall, or rate limiter intercepted the request and served an HTML page.
The fix is not in your JSON. Log the raw response text before parsing it:
const res = await fetch(url);
const text = await res.text();
console.log(res.status, text.slice(0, 200)); // see what you actually got
const data = JSON.parse(text);
If text starts with <!DOCTYPE html>, you found it. Check the status code and the URL. Also confirm the response Content-Type is application/json, not text/html.
Cause 2: Trailing comma#
JSON does not allow a comma after the last item in an object or array. JavaScript object literals do, which is exactly why this slips through. You write JSON by hand the way you write JS, and the parser rejects it.
{
"name": "Ada",
"role": "engineer", // this trailing comma is invalid
}
Remove the comma after "engineer". The same applies to arrays: [1, 2, 3,] is invalid JSON. A formatter flags these instantly because the offending comma is right before a } or ].
Cause 3: Single quotes instead of double quotes#
JSON requires double quotes for both keys and string values. Single quotes are a JavaScript habit that JSON does not share.
{ 'name': 'Ada' } // invalid: single quotes everywhere
{ "name": "Ada" } // valid
If you control the source, switch every ' to ". If the bad string came from somewhere you cannot edit, do not regex-replace blindly (apostrophes inside values will break). Run it through a tool that understands the structure instead.
Cause 4: The invisible BOM (and Windows CRLF)#
This one stumps people for hours because the JSON looks perfect. You see valid JSON, you paste it, and the parser still throws at position 0. The culprit is a byte order mark, a hidden U+FEFF character some editors (and PowerShell's > redirect on Windows) prepend to UTF-8 files.
The parser reads the BOM as the first token and rejects it. To confirm, check the first bytes:
const text = fs.readFileSync("data.json", "utf8");
console.log(text.charCodeAt(0)); // 65279 means a BOM is present
const clean = text.replace(/^/, "");
JSON.parse(clean);
Save the file as UTF-8 without BOM in your editor (VS Code shows the encoding in the status bar; click it and pick "Save with Encoding"). On Windows, prefer Out-File -Encoding utf8NoBOM or Set-Content over the > redirect, which can inject the BOM. Stray carriage returns from CRLF line endings can cause similar position-0 weirdness when JSON is concatenated or streamed.
Cause 5: Unquoted keys#
In JavaScript, { name: "Ada" } is fine. In JSON, the key must be quoted: { "name": "Ada" }. This happens most when someone copies a JS object literal into a .json file or an API body.
The fix is to wrap every key in double quotes. If you have a large object, a formatter will not auto-quote keys for you (that would be guessing at your intent), but it will point at the exact key that failed so you can fix it fast.
Cause 6: Comments#
JSON has no comments. Not //, not /* */. People add them to config files out of habit and then a strict parser chokes.
{
// app settings <- invalid, JSON forbids comments
"port": 3000
}
Strip the comments. If you genuinely need commented config, use a format built for it (JSON5 or JSONC, which VS Code uses for its own settings) and parse it with a library that supports that dialect, not plain JSON.parse.
How to Find the Exact Failing Line and Column#
This is the skill that turns a 30-minute hunt into a 30-second fix, and it is the part most search results skip entirely. The error gives you a position; here is how to convert it into a real location.
Step 1: Read the position or line/column from the error#
Capture the full error, not just the first word. In Node or the browser, wrap the parse:
try {
JSON.parse(raw);
} catch (e) {
console.error(e.message); // includes position or line:column
}
Firefox already gives you line X column Y. V8 gives you a byte position. Note that number, you will use it next.
Step 2: Jump to that position in the raw string#
If you have a byte position (say 142), slice around it to see the context the parser saw:
console.log(raw.slice(130, 160));
The character at the boundary is your culprit. Seeing 15 characters on each side almost always makes the cause obvious: a stray comma, a single quote, an HTML tag.
Step 3: Paste it into a validator that highlights the spot#
Eyeballing positions in a long string is slow and error-prone. Paste the raw JSON into the JSON formatter and validator, which beautifies the structure and marks the failing line with a clear message about what it expected. The reformat itself often reveals the problem, because a misplaced comma or unclosed bracket becomes visually obvious once the nesting is indented properly.
Step 4: Fix, re-validate, then re-test in code#
Apply the fix, validate again to confirm the string is now clean, then run your actual code path. If the JSON came from an API, fix it at the source if you can rather than patching the string client-side. A clean response beats a defensive .replace() chain every time.
Preventing the Error Before It Happens#
Most of these are avoidable with a few habits. The goal is to never hand-write JSON that your tools can generate correctly for you.
- Never build JSON by string concatenation. Use
JSON.stringify()so quoting and commas are always correct. If you are unsure how the output should look, our guide on how to format JSON in JavaScript coversJSON.stringifyand its sharp edges. - Always log the raw response before parsing an API result, so a
<(HTML) shows up immediately instead of as a mysterious token error. - Check the HTTP status first. If
res.okis false, read it as text and surface the error page rather than blindly parsing. - Save config files as UTF-8 without BOM and keep a formatter in your daily workflow. If you live in JSON, see why the JSON formatter is a developer daily tool for catching these issues before they ship.
- Lint your JSON in CI. A schema or a simple parse step in your pipeline catches a trailing comma before it reaches production.
Conclusion: Read the Position, Then Match the Cause#
An unexpected token in JSON error feels random until you realize it is one of six predictable problems, and the parser already told you where to look. Start with the position or line and column, then match the failing character to the cause: < means HTML, a comma before } means a trailing comma, a ' means single quotes, and a position-0 error on perfect-looking JSON almost always means a hidden BOM.
When you want to stop squinting at byte offsets, paste the string into a validator that highlights the exact line and shows what it expected. That single step replaces most of the manual hunting and gets you back to building.
Frequently Asked Questions#
What does "Unexpected token < in JSON at position 0" mean?
It means the response you tried to parse starts with a <, which is the opening of an HTML tag, not JSON. Your code expected JSON but the server returned an HTML page, usually a 404 or 500 error page, a login redirect, or the wrong URL. Log the raw response text and check the status code to confirm.
Why does my valid-looking JSON throw an error at position 0?
The most common cause is an invisible byte order mark (BOM), a hidden U+FEFF character some editors and Windows shells add to UTF-8 files. The JSON looks perfect but the parser reads the BOM first and rejects it. Strip it with text.replace(/^/, "") or save the file as UTF-8 without BOM.
Are trailing commas allowed in JSON?
No. A comma after the last item in an object or array is invalid JSON, even though JavaScript object literals allow it. This is one of the most frequent causes of unexpected token errors. Remove the comma sitting directly before any } or ].
Can I use single quotes in JSON?
No. JSON requires double quotes for every key and every string value. Single quotes are valid in JavaScript but not in JSON, so { 'name': 'Ada' } will throw, while { "name": "Ada" } parses correctly.
How do I find the exact line causing the JSON error? Read the position or line and column from the error message first, since the parser tells you where it failed. Slice the raw string around that position to see the context, or paste the whole string into the JSON formatter and validator to highlight the failing line and show what character it expected there.
Does JSON support comments?
Plain JSON does not support comments of any kind, so // and /* */ will cause an unexpected token error. If you need commented configuration, use JSON5 or JSONC (the dialect VS Code uses for its settings) and parse it with a library that understands those formats rather than JSON.parse.



