JSON errors

JavaScript, from JSON.parse(), fetch() with response.json() and axios

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

The first character JSON.parse saw was <, and no JSON document can start with that. It almost always means the server sent back an HTML page where your code expected JSON, so the real problem is in the request, the URL or the server, and the JSON itself is fine.

How each browser and language words it

Chrome, Edge and Node 20+
SyntaxError: Unexpected token '<', "<!DOCTYPE "... is not valid JSON
Older Chrome and Node 18 and below
SyntaxError: Unexpected token < in JSON at position 0
Firefox
SyntaxError: JSON.parse: unexpected character at line 1 column 1 of the JSON data
Safari
SyntaxError: JSON Parse error: Unrecognized token '<'

Paste your JSON to find the exact line

This is what JSON.parse was handed, a 404 page. The fix is in the request, so Repair cannot help here.

Skip to editor
Explorer
Open Files

No files open

0 files open

jsoneditor.io v2

<!DOCTYPE html>
<html lang="en">
<head>
  <title>404 Not Found</title>
</head>
<body>
  <h1>Not Found</h1>
  <p>The requested URL /api/order was not found on this server.</p>
</body>
</html>
JSONUTF-8
Spaces: 2

Causes and how to fix each one

1. The URL is wrong and you got a 404 page

A typo in the path, a missing /api prefix or a relative URL that resolves somewhere else all return the server's HTML 404 page. Open the request in your browser's Network tab and look at the Response, if it starts with <!DOCTYPE html> you're hitting the wrong address.

2. The server crashed and returned an error page

Frameworks like Express, Django, Rails and Laravel render an HTML error page on a 500, and reverse proxies like nginx do the same for a 502 or 504. Check the status code before you parse, so a failed request throws a clear error instead of a confusing parse error.

JavaScript
const response = await fetch('/api/orders')

if (!response.ok) {
  throw new Error(`Request failed with status ${response.status}`)
}

const type = response.headers.get('content-type') ?? ''
if (!type.includes('application/json')) {
  const body = await response.text()
  throw new Error(`Expected JSON but got ${type}: ${body.slice(0, 120)}`)
}

const data = await response.json()

3. Your dev server is serving index.html for API routes

Single page app dev servers (Vite, Create React App, webpack) answer any unknown path with index.html. If your API runs on another port and the proxy isn't set up, /api/orders comes back as your app's HTML. Point the request at the API's real origin or configure the dev server proxy.

4. A login or session redirect

When a session expires, many apps redirect to a sign-in page. fetch follows the redirect and hands you the login page's HTML. Check response.redirected or response.url, and handle 401 and 403 before parsing.

Questions people ask

What does "at position 0" mean?

Position 0 is the very first character of the text. The parser failed before reading anything valid, which is why this error nearly always points at a response that was never JSON in the first place.

How do I see what the server actually returned?

Open your browser's developer tools, go to the Network tab, click the request and look at the Response panel. In code, call response.text() instead of response.json() and log the first few hundred characters.

Why does it work in Postman but not in my app?

Postman sends the request exactly as you typed it. Your app may be adding a base URL, missing an auth header or cookie, or going through a dev server proxy, any of which can turn the same request into an HTML page.

Tools for this