Unexpected token < in JSON at position 0: What It Means and How to Fix It

If you have hit Unexpected token < in JSON at position 0, your code tried to parse JSON and got back something that starts with a less-than sign. Nine times out of ten, that character is the opening of an HTML page. The first bytes are <!DOCTYPE html> or <html>. Your request expected JSON, and the server returned an error page, a login redirect, or a plain 404 instead.

The message is blunt because the parser reads the very first character, sees a <, and stops. “Position 0” is the giveaway: the response was never JSON in the first place. This guide explains what actually triggers the error, how to confirm the cause in under a minute, and the fixes that hold up in production.

Why a < means you got HTML, not JSON

Valid JSON only ever starts with {, [, a quote, a digit, or the letters in true, false, and null. It never starts with <. So when the parser complains about a < at position 0, the body it received was markup, not data. The usual sources:

Every one of these returns a web page. The JSON parser is simply the first place that page causes a visible failure.

Confirm the cause in under a minute

Do not guess. Look at the raw response before you parse it. Read it as text first, not as JSON:

const res = await fetch('/api/users');
const text = await res.text();       // read as text, not res.json()
console.log(res.status, res.headers.get('content-type'));
console.log(text.slice(0, 120));     // see what actually came back

If the text starts with <!DOCTYPE html> or <html>, the endpoint returned a page, not data. The content-type header will usually say text/html instead of application/json, which confirms it. In the browser, the Network tab shows the same story: open the request, click Response, and read the first line.

Fix the request, not the parser

Once you know the body is HTML, the parse error is only a symptom. Fix what produced the wrong response.

Check the status code before parsing. A failed request should never reach JSON.parse:

const res = await fetch('/api/users');
if (!res.ok) {
  throw new Error(`Request failed with ${res.status}`);
}
const data = await res.json();       // only parse a confirmed success

Check the URL and base path. Make sure you are hitting the API and not the front-end router’s catch-all route that serves HTML for unknown paths.

Handle authentication. If the body is a login page, refresh the token or re-authenticate before retrying the request.

Read the content type. Only call res.json() when the response advertises application/json. Otherwise, surface the text so the real error is visible instead of hidden behind a parser exception.

The other “Unexpected token” variants

When the position is something other than 0, the response probably is JSON, just malformed. These are the variants developers hit most, with the cause and the fix for each. The quickest way to locate any of them is to paste the payload into the JSON parser, which reports the exact line and character where parsing breaks.

Error message Cause Fix
Unexpected token < … position 0 HTML returned instead of JSON Fix the request; guard on the status code
Unexpected token ' Single quotes instead of double JSON requires double quotes on keys and strings
Unexpected token } A trailing comma before a closing brace or bracket Remove the trailing comma
Unexpected token o in JSON You passed an object, not a string Do not parse a value that is already parsed
Unexpected end of JSON input A truncated or empty body Confirm the full response arrived
Unexpected token / A // or /* */ comment JSON has no comments; strip them

Two of these deserve a note. Unexpected token o means you called the parser on a value that was already a JavaScript object: it gets converted to the string [object Object], and the parser chokes on the first o. And a hidden byte-order mark or stray whitespace can also break position 0, which is hard to spot by eye but obvious once you run the text through the JSON formatter or the JSON viewer.

How other languages report the same error

The wording is JavaScript’s, but every language has an equivalent:

The habit is the same everywhere: parse defensively, and inspect the raw text whenever parsing fails instead of assuming the data was valid.

A repeatable debugging routine

When this error appears, run the same three steps every time:

  1. Read the response as text and log the first 100 characters. HTML announces itself on the first line.
  2. Check the status code and content type. A non-success status or a text/html type means the body was never meant to be JSON.
  3. If it really is JSON, validate it. Paste it into the JSON parser to jump to the exact line and column, then fix the quote, comma, or bracket it flags. To make an escaped or minified payload readable first, run it through the JSON formatter or the JSON pretty-print tool, or clean up stray characters with JSON escape.

Guard your requests and inspect what you actually received, and this error stops being a mystery. It is the parser telling you, correctly, that it was handed a web page.

Frequently asked questions

Does “position 0” always mean HTML?
Almost always. Position 0 means the first byte was already invalid JSON, and the most common invalid first byte is the < that opens an HTML document. A leading byte-order mark or stray whitespace can occasionally cause it too.

Why does my code get HTML when the API works fine in the browser?
Usually authentication. Your browser holds a valid session cookie; your code does not, so the server redirects to an HTML login page instead of returning JSON.

How do I stop it from crashing my app?
Check the response status before parsing, and wrap the parse call in try/catch. Never parse a response you have not confirmed is a successful JSON payload.

The error points at a later position, not 0. Is it still HTML?
No. A non-zero position means the body is JSON but malformed somewhere inside it. Run it through the JSON parser to find the exact character.

Leave a Reply

Your email address will not be published. Required fields are marked *