Unexpected token '<', "<!DOCTYPE "... is not valid JSON

ErrorJavaScript

SyntaxError: Unexpected token '<', "<!DOCTYPE "... is not valid JSON

Where it stops

<!DOCTYPE html>
<html>
  <head><title>404 Not Found</title></head>

What it means

Your code asked for JSON and got an HTML page instead. <!DOCTYPE html> is how an HTML page starts, and no JSON document can start with <. Nothing is wrong with your parsing code. The problem is the response, so the fix is to find out why the server sent a page.

Last verified on Chromium 145, Firefox 146 and WebKit 26.

The validator opens with the error marked. Examples travel in the URL fragment, which browsers never send to a server.

Express Lint formats broken JSON so you can find the error, and marks the line where it stops. It changes only spaces and line breaks outside strings. Strings in either quote stay as written. If the quotes do not pair up, it leaves your text as it is. Ctrl+Z undoes it. See how it works.

Usually
The URL is wrong, so you got the 404 pageA single-page app answered with index.htmlThe request needed a loginThe server failed and sent its error page
Fix
Check the response before you parse it.

The fix

Check the response before you parse it. response.ok is false for every error status, and the Content-Type header should include application/json. When either check fails, read the body with response.text() and log it, so you can see the page that came back. Then fix whatever it points to: the URL, the proxy or the login.

broken.jsonFails to parse
<!DOCTYPE html>
<html>
  <head><title>404 Not Found</title></head>
  <body>Not Found</body>
</html>
JSON.parse stops at line 1, column 1.

5 lines change.

fixed.jsonParses
{
  "id": 42,
  "name": "Ada"
}
JSON.parse reads it.

How the message appears

Chrome and Node first, then Firefox, then Safari. The two lines after those are what response.json() says in Chrome and in Safari. Safari does not mention JSON at all there. Every one of these is the same failure: the first character was <.

Console
  • SyntaxError: Unexpected token '<', "<!DOCTYPE "... is not valid JSONChrome and Node
  • SyntaxError: JSON.parse: unexpected character at line 1 column 1 of the JSON dataFirefox
  • SyntaxError: JSON Parse error: Unrecognized token '<'Safari
  • SyntaxError: Failed to execute 'json' on 'Response': Unexpected token '<', "<!DOCTYPE "... is not valid JSON
  • SyntaxError: The string did not match the expected pattern.
  • SyntaxError: Unexpected token < in JSON at position 0Older V8 wording

What causes it

  1. The URL is wrong, so you got the 404 page

    A typo in the path, a missing /api prefix, or a trailing slash the server treats differently. Most frameworks answer an unknown URL with an HTML error page, not a JSON one. Open the exact URL from the Network tab in a new browser tab and you will see the page your code tried to parse.

  2. A single-page app answered with index.html

    Dev servers like Vite, and static hosts with a catch-all rewrite, answer every unknown path with index.html so client-side routing works. Ask for /api/users when no API is running there and you get the app shell with a 200. The status looks fine and the body is HTML.

  3. The request needed a login

    An expired session or a missing token often gets a redirect to the login page rather than a 401. fetch follows redirects by default, so your code receives the login form.

  4. The server failed and sent its error page

    A 500 from your framework, a 502 from a proxy or a 503 from a load balancer usually comes with an HTML body. fetch does not reject on an error status, so response.json() runs on that page and throws this instead of telling you about the 500.

Worth knowing

NoteA body can only be read once. If you want the text for your logs when parsing fails, call response.text() first and then JSON.parse(text) inside a try, rather than calling response.json().

NoteSafari words this differently. JSON.parse says Unrecognized token '<', and response.json() says The string did not match the expected pattern., which gives no hint that JSON is involved. It is the same problem.

NoteThe same failure in other languages: Python says Expecting value: line 1 column 1 (char 0) and Go says invalid character '<' looking for beginning of value. Both are in this reference: the Python page and the Go page.

What you put into the tools never leaves your browser. We count page views and keep them for 90 days. Accept and we also remember this browser, so a return visit is not counted as new. Privacy policy