Quick answer:
Unexpected token ... in JSON at position 0means the text handed toJSON.parse()is not JSON at all; it fails on the very first character. That character names the cause:<means the server returned HTML,umeans you parsedundefined,omeans you parsed an object ([object Object]),means the file has a byte order mark, and'means single quotes. Log the raw text before parsing and the fix is usually obvious.
This error does not tell you that your JSON has a typo somewhere. It tells you that whatever you parsed never was JSON. Position 0 is the giveaway: a document with a stray comma fails at position 847, not at the first byte. So the job is not to proofread the JSON. The job is to find out what text you actually received, and the token in the message has already done half the work.
Repair a copy of the failing payload with the JSON Fixer, then review the changes before using the result.
Read the Token, Not the Position
The message has two variants depending on the JavaScript engine version. Older V8 (Chrome, Edge, Node) prints the short form; newer V8 prints a snippet of the input, which is a big improvement:
// Older form
SyntaxError: Unexpected token < in JSON at position 0
// Newer form
SyntaxError: Unexpected token '<', "<!DOCTYPE " is not valid JSON
// Firefox
SyntaxError: JSON.parse: unexpected character at line 1 column 1 of the JSON data
Whichever form you see, the first character after "token" maps to a cause:
| Token | What you parsed | Where it came from |
|---|---|---|
< | HTML page instead of JSON | Login redirect, 404 page, proxy error, wrong URL |
u | The string "undefined" | JSON.parse(undefined), missing variable, empty storage key |
o | The string "[object Object]" | Object passed where a JSON string was expected |
\ufeff | UTF-8 byte order mark | File saved by Notepad, PowerShell, or Excel |
' | Single-quoted JSON | Hand-written JSON or a Python dict printed with str() |
N | The word NaN or null-ish text | JSON.parse(NaN) or a number that failed to compute |
/ | A comment | Config file with // or /* */ comments |
} | or ] : trailing comma before it | Hand-edited file; the position is usually not 0 |
The sections below take the five common ones in turn: how to confirm, then how to fix.
Token <: The Server Sent HTML
By a wide margin the most frequent case. Your code called an API and got a web page. The < is the start of <!DOCTYPE html> or <html>.
Confirm it
Open the browser DevTools Network tab, click the request, and look at the Response tab. You will see the page. Common pages that show up here:
- A login page: the session or token expired, and the server redirected to sign-in with a 200 status.
- A 404 page: the URL is wrong. A typo in the path, a missing
/apiprefix, or a dev server that servesindex.htmlfor every unknown route (the single-page app fallback) are the classics. - A 502 or 504 page from nginx, Cloudflare, or a load balancer, meaning the API behind it was down.
- A captive portal or corporate proxy page, if the user is on hotel or office Wi-Fi.
In code, replace the parse with a text read while you investigate:
const res = await fetch("/api/items");
const text = await res.text();
console.log(res.status, res.headers.get("content-type"));
console.log(text.slice(0, 200));
Fix it
Fix the underlying cause (the URL, the auth, the proxy), then make the client refuse to parse non-JSON so the next occurrence produces a clear error instead of this one:
const res = await fetch("/api/items");
const type = res.headers.get("content-type") || "";
if (!res.ok || !type.includes("application/json")) {
throw new Error("Expected JSON, got " + res.status + " " + type);
}
const items = await res.json();
The fetch JSON guide builds this into a reusable helper with timeouts and retries.
Token u: You Parsed undefined
JSON.parse(undefined) coerces its argument to the string "undefined" and fails on the u. Nothing was fetched or read; the variable simply had no value.
const saved = localStorage.getItem("cart"); // null if never set
JSON.parse(saved); // null parses fine, returns null
const settings = {};
JSON.parse(settings.theme); // undefined -> Unexpected token u
Note the asymmetry: JSON.parse(null) works, because String(null) is "null", which is valid JSON. undefined has no such luck.
Confirm it
console.log(typeof value, value) right before the parse. If it prints undefined undefined, trace where the value should have been assigned: a misspelled property name, a function that forgot to return, an async result used before it resolved.
Fix it
const raw = localStorage.getItem("cart");
const cart = raw ? JSON.parse(raw) : [];
Guard the parse, and fix the source so the value exists. If the raw value is an empty string rather thanundefined, you get a different message, Unexpected end of JSON input, with the same guard as the fix.
Token o: You Parsed an Object
This one usually reports position 1, not 0, and that detail is diagnostic. JSON.parse(someObject) coerces the object to "[object Object]". The parser reads [ as the start of an array, then hits o and stops.
const user = { name: "Ada" };
JSON.parse(user);
// SyntaxError: Unexpected token o in JSON at position 1
// newer: Unexpected token 'o', "[object Object]" is not valid JSON
Confirm it
The data is already parsed. This happens when a library (Axios, a fetch wrapper, a message bus) has parsed the response for you and your code parses it again, or when you JSON.parse a value that was never stringified in the first place.
Fix it
Delete the JSON.parse. If you need to tolerate both forms (some callers pass strings, some pass objects), check first:
const data = typeof input === "string" ? JSON.parse(input) : input;
The same mistake in the other direction, sending an object as a request body without JSON.stringify, puts the literal text [object Object] on the wire and gives the server this error instead of you.
Token : A Byte Order Mark
The token is invisible. The message will show either or, in some engines, an empty pair of quotes with nothing between them. The file begins with the three bytes EF BB BF, a UTF-8 byte order mark that the JSON spec says must not be present and that JSON.parse will not skip.
Confirm it
console.log(text.charCodeAt(0)); // 65279 means BOM
console.log(JSON.stringify(text.slice(0, 5))); // "\ufeff{\"id" makes it visible
On the command line, xxd file.json | head -1 shows efbb bf7b at the start. The usual sources are Windows Notepad in older versions, PowerShell's Out-File and > redirection, and Excel's CSV export when a CSV was converted to JSON.
Fix it
// At read time
const clean = text.replace(/^\uFEFF/, "");
const data = JSON.parse(clean);
// In Python, let the codec strip it
with open("file.json", encoding="utf-8-sig") as f:
data = json.load(f)
Then fix the file at the source: in VS Code the encoding indicator in the status bar says "UTF-8 with BOM"; click it and choose "Save with Encoding" then plain UTF-8. The UTF-8 and BOM guide covers the other places it hides.
Token ': Single Quotes
JSON strings and keys must use double quotes. {'name': 'Ada'} is a valid JavaScript object literal and a valid Python dict, and invalid JSON. The parser fails on the first ' after the brace, so the position is 1 rather than 0 if there is no leading whitespace.
Confirm it
Look at the text. If it was produced by printing a Python dict with print(d) or str(d), that is the cause; Python's repr uses single quotes and writes True and None, neither of which is JSON either.
Fix it
Produce the text with a real serializer: json.dumps(d) in Python, JSON.stringify(obj) in JavaScript. Do not fix it with .replace(/'/g, '"'); that breaks any value containing an apostrophe. If you have a one-off file to repair, paste it into JSON Prettifier, which points at the first offending quote so you can correct the file by hand, then validate again.
Less Common Tokens
- Token N: the text is
NaN, usually fromJSON.parse(someNumber)where the arithmetic failed. JSON has no NaN, Infinity, or undefined; only finite decimal numbers are valid. - Token /: a comment at the top of the file. JSON forbids comments; see comments in JSON for the alternatives and the parsers that allow them anyway.
- Token } or ] at a high position: a trailing comma just before the closing bracket. That is not a position-0 problem; it is a syntax slip inside otherwise valid JSON, and the fix is to delete the comma.
- Token a, e, i and other letters: often the start of an unquoted key or value such as
{id: 1}, or a plain-text error message likeerror: not foundreturned by a server that forgot to set JSON.
A Debugging Checklist
- Read the token in the message. Match it against the table above.
- Log the input before the parse, wrapped in
JSON.stringify()so invisible characters show. - For network responses, check status and
Content-Typein the Network tab. HTML means the request, not the parser, is the problem. - If the input looks like JSON but still fails, paste it into a validator to get the real line and column. Position 0 errors in such cases are almost always a BOM.
- Add a guard so that the next failure produces an error that names the cause (status code, content type, missing value) rather than a token.
If your error has a large position number instead of 0 or 1, you have a different problem: valid JSON with a mistake in the middle. The general JSON parse error guide and the list of common JSON errors cover those.