Fixing "Unexpected token in JSON at position 0"

Suman SharmaSuman Sharma

You called response.json() or JSON.parse() and got one of these:

SyntaxError: Unexpected token '<', "<!DOCTYPE "... is not valid JSON SyntaxError: Unexpected token < in JSON at position 0 SyntaxError: Unexpected end of JSON input

The parser is telling you exactly what's wrong: the text you gave it isn't JSON. The useful clues are the token and the position.

Read the token first

TokenWhat you receivedUsual cause
< at position 0HTML404, 500, login page, wrong URL, dev server fallback
u at position 0the string undefinedYou parsed a variable that was never set
o at position 1[object Object]You parsed something that was already an object
end of JSON inputempty string204 response, empty body, network cut
, or } late in the stringalmost-JSONtrailing comma or comment in a JSON file
 (invisible) at 0a byte order markfile saved as "UTF-8 with BOM"

Step 1: Look at the real response

Swap .json() for .text() for a moment:

const res = await fetch("/api/user"); const body = await res.text(); console.log(res.status, res.headers.get("content-type"), body.slice(0, 300));

Nine times out of ten you'll see 404 and <!DOCTYPE html>. You can also open DevTools → Network, click the request, and read the Response tab.

Step 2: Fix the cause

The URL is wrong

A relative URL like fetch("api/user") (no leading slash) resolves against the current page, so on /dashboard/settings it requests /dashboard/api/user. That returns your app's HTML 404 page. Use /api/user.

In single-page apps the dev server often returns index.html for any unknown path, so a typo returns HTML with a 200 status, which is extra confusing.

The server threw an error

A 500 from many frameworks is an HTML error page. Fix the server error, and make your API return JSON errors:

// Next.js route handler return Response.json({ error: "Not found" }, { status: 404 });

You're not logged in

An expired session can redirect to a login page. fetch follows the redirect and you parse the login HTML. Check res.redirected or the final res.url.

The body is empty

Some endpoints return 204 No Content. Don't parse those:

const data = res.status === 204 ? null : await res.json();

It's already an object

JSON.parse(data); // data is already { ... } → "[object Object]" is not valid JSON

Axios and many SDKs parse JSON for you. Use the value directly.

Step 3: Write a safer fetch helper

Check the status and content type before parsing so you get an error you can actually read:

export async function getJson<T>(url: string, init?: RequestInit): Promise<T> { const res = await fetch(url, init); const type = res.headers.get("content-type") ?? ""; if (!res.ok || !type.includes("application/json")) { const text = await res.text(); throw new Error(`${res.status} from ${url}: ${text.slice(0, 200)}`); } return res.json() as Promise<T>; }

When the problem is a JSON file

If you're parsing a file rather than a response, the usual culprits are a trailing comma, a comment, single quotes, or unquoted keys. All four are valid JavaScript but invalid JSON. Paste the file into the JSON Formatter and it will point to the line. If you want comments in config, see JSON vs YAML vs TOML.

Frequently asked questions

What does 'Unexpected token < in JSON at position 0' mean?

The first character of the response was <, which means the server sent HTML, usually a 404 page, a 500 error page or a login redirect, instead of JSON.

What does 'Unexpected end of JSON input' mean?

The response body was empty or cut off. Common causes are a 204 No Content response, a server that crashed mid-response, or calling response.json() on a request that returned nothing.

How do I see what the server actually returned?

Call response.text() instead of response.json() and log it, or open the request in the browser DevTools Network tab and look at the Response panel.

Contact

Get in Touch

Want to call me? book a call and I'll respond in the available time slot. I will ignore all soliciting.