Skip to content
JSON Tidy
JSON errors

Unexpected token '<' in JSON

Updated by John Marshall

You’ll usually run into this when you call response.json() or JSON.parse on something a server sent back. Current versions of Chrome and Node.js word it as Unexpected token '<', "<!DOCTYPE "... is not valid JSON, and older ones as Unexpected token < in JSON at position 0. Either way, that < is the start of an HTML page. You asked for JSON and got a web page instead, most often a 404 page, a server error page, or a login page because the request wasn’t signed in. There’s nothing wrong with your parsing code, and nothing in the response to repair. What you need to find out is why the server sent that page.

Quick fix

  1. Open your browser’s developer tools, go to the Network tab, and click the failing request to see its response.
  2. Check the status. A 404 means the URL is wrong. A 401, a 403, or a redirect to a sign-in page means the request isn’t authenticated. Anything from 500 to 504 is a server error.
  3. Or paste the response below to see which page it is.

Fix your JSON

Runs in your browser. Nothing is uploaded.

Why the server sent HTML

It’s almost always one of these.

The URL is wrong. A missing /api prefix or a small typo gets you the site’s 404 page. Watch out for relative URLs too: fetch("api/users") on a page at /settings/ actually requests /settings/api/users. Starting the path with / avoids that.

You got your own app’s index.html. Development servers for single-page apps, such as Vite, answer any unknown path with index.html and a 200 status, so nothing looks wrong until you parse it. If your API runs on a different port, set up the dev server’s proxy, or call the API by its full URL.

You were sent to a sign-in page. The session expired, or the request didn’t include its credentials. The server redirected to the login page, and fetch followed the redirect automatically.

The server or a proxy failed. A 500 from your framework, or a 502 or 504 from nginx or a load balancer, often comes back as an HTML page. Rate-limit and bot-check pages from a CDN look the same.

“Malformed JSON in manifest”

You’ll see this version when a web app manifest, the file named in <link rel="manifest">, comes back as HTML. It’s the same problem: the manifest isn’t at that address, so the server sent a 404 page or, in a single-page app, its index.html.

Open the manifest’s URL directly in the browser. If you see a web page instead of JSON, fix the path. The usual causes are a manifest kept in src/ instead of the folder your build serves as-is (public/ in Vite, Next.js and Create React App), and a relative href that breaks on pages below the root. Starting it with /, as in href="/manifest.json", fixes the second one, unless the app is served from a subfolder. In that case, include the subfolder in the path.

Make the error tell you what happened

response.json() parses the body without checking the status code or content type, so a failed request shows up as a confusing JSON error. Reading the text yourself and checking the response gives you an error that actually says what the server sent:

const response = await fetch("/api/users");
const body = await response.text();

if (!response.ok) {
  throw new Error(`Request failed with ${response.status}: ${body.slice(0, 200)}`);
}
if (!response.headers.get("content-type")?.includes("application/json")) {
  throw new Error(`Expected JSON, got: ${body.slice(0, 200)}`);
}
const users = JSON.parse(body);

Is your message about a different character? An o or a u means you passed an object or undefined, which is covered in "[object Object]" is not valid JSON. A single quote is covered in Expecting property name.

The same error elsewhere

Chrome, Edge, Node.js 20+Unexpected token '<', "<!DOCTYPE "... is not valid JSON
Older Chrome, Node.js 18Unexpected token < in JSON at position 0
FirefoxJSON.parse: unexpected character at line 1 column 1 of the JSON data
SafariJSON Parse error: Unrecognized token '<'
PythonJSONDecodeError: Expecting value: line 1 column 1 (char 0)
Goinvalid character '<' looking for beginning of value