SyntaxError: Unexpected token means JavaScript was reading something, either your code or a JSON string, and found a character it didn't expect at that point. The fix depends on which of the two it was reading, and the full message usually tells you.
This guide shows the exact messages for each common cause and how to fix them. Every message below was produced by Chrome, Node.js 20 and Safari's engine (WebKit) in October 2026, so you can match yours word for word.
First: Code Error or JSON Error?
| If the message says… | It's a… | Jump to |
|---|---|---|
| …is not valid JSON / in JSON at position / end of JSON input | JSON error at runtime: the data isn't JSON | Unexpected token '<' and the JSON sections |
| Safari: JSON Parse error / The string did not match the expected pattern | Same, in Safari | The JSON sections |
| Unexpected token '{' / ')' / ';' / 'else' / identifier | Syntax error in your code: the file doesn't run at all | Syntax Errors in Your Code |
| Invalid or unexpected token | An invalid character in your code (often curly quotes) | Syntax Errors in Your Code |
| Parsing error: Unexpected token | ESLint can't parse JSX or TypeScript | ESLint Parsing Errors |
Unexpected token '<' (Usually HTML Instead of JSON)
This is the most common one. < is the first character of an HTML page (<!DOCTYPE html>), so the code expected JSON and got HTML:
const user = await fetch("/api/user").then((res) => res.json());
// If the server sends an HTML page instead of JSON:
// Chrome, Node: SyntaxError: Unexpected token '<', "<!DOCTYPE "... is not valid JSON
// Safari: SyntaxError: The string did not match the expected pattern.In our tests this happened for both a 404 page and an HTML page served with status 200. Common reasons:
- Wrong URL: a typo or a missing /api prefix, so the server returns its 404 page.
- Single-page app fallback: many single-page app setups, including Vite's dev server, answer unknown paths with index.html and status 200.
- Login redirect or error page: the API redirected to a sign-in page or crashed and returned an HTML error page.
- A script that's really HTML: if <script src="/app.js"> gets an HTML page back, the browser reports Uncaught SyntaxError: Unexpected token '<'. Check the script's URL in the Network tab.
- JSX that wasn't compiled: const el = <h1>Hi</h1>; in a plain .js file run directly by the browser or Node also gives Unexpected token '<'. JSX needs a build tool (Vite, Next.js, Babel).
Fix: check the response before parsing it
Read the body as text first, then parse it, so the error shows what actually came back:
// fetch + JSON with errors that say what actually came back.
export async function getJSON(url, options) {
const res = await fetch(url, options);
const text = await res.text(); // read as text first, so we can show it on failure
if (!res.ok) {
throw new Error(`${res.status} ${res.statusText} from ${url}: ${text.slice(0, 100)}`);
}
if (!text) return null; // 204 No Content or an empty body
try {
return JSON.parse(text);
} catch {
const type = res.headers.get("Content-Type");
throw new Error(`Expected JSON from ${url} but got ${type}: ${text.slice(0, 100)}`);
}
}try {
const user = await getJSON("/api/user");
} catch (err) {
console.error(err.message);
// 404 Not Found from /api/missing: <!DOCTYPE html><html><body>Not here</body></html>
// or: Expected JSON from /api/html but got text/html; charset=utf-8: <!DOCTYPE html>...
}These are the messages our helper produced for the 404 and the HTML page; it also returns null for an empty response. More on fetch in our Fetch API guide. If the request is blocked instead (Failed to fetch), see the CORS guide.
Unexpected end of JSON input
The text being parsed was empty or cut off. With fetch it usually means the server sent no body:
// Server answered 204 No Content (or 200 with an empty body)
await fetch("/api/empty").then((res) => res.json());
// Chrome: SyntaxError: Failed to execute 'json' on 'Response': Unexpected end of JSON input
// Safari: SyntaxError: The string did not match the expected pattern.Check for an empty body before parsing (the getJSON helper above does), or don't call .json() for requests that return 204. Safari's message, The string did not match the expected pattern., doesn't mention JSON at all, which is why it confuses so many people; in our test it appeared for HTML responses and empty ones alike.
JSON.parse Errors and What They Mean
JSON is stricter than JavaScript object syntax. Here's what each common mistake produces:
| Input to JSON.parse | Chrome / Node.js | Safari | Fix |
|---|---|---|---|
| { name: "John" } | Expected property name or '}' in JSON at position 2 | JSON Parse error: Expected '}' | Quote keys: {"name": "John"} |
| { 'name': 'John' } | Expected property name or '}' in JSON at position 2 | Single quotes (') are not allowed in JSON | Use double quotes |
| { "name": "John", } | Expected double-quoted property name in JSON at position 18 | Property name must be a string literal | Remove the trailing comma |
| JSON with // comments | Expected double-quoted property name in JSON at position … | Unrecognized token '/' | Remove comments (JSON has none) |
| an object, not a string | "[object Object]" is not valid JSON | Unexpected identifier "object" | It's already parsed; don't call JSON.parse |
| undefined | "undefined" is not valid JSON | Unexpected identifier "undefined" | Check the value exists first |
| John (a plain string) | Unexpected token 'J', "John" is not valid JSON | Unexpected identifier "John" | Strings need quotes: "\"John\"" |
| "" (empty string) | Unexpected end of JSON input | Unexpected EOF | Check for empty text |
Two details that surprise people: JSON.parse(null) doesn't throw (it returns null), and the "position" in Chrome's message counts characters from 0, so position 2 is the third character. Newer Chrome versions also add the line and column.
When the input might be bad, for example a value from localStorage, parse it safely with a fallback:
// JSON.parse that returns a fallback instead of throwing.
export function parseJSON(text, fallback = null) {
if (typeof text !== "string") return fallback; // objects, undefined, null
try {
return JSON.parse(text);
} catch (err) {
console.warn("Invalid JSON:", err.message, text.slice(0, 100));
return fallback;
}
}
// Example: settings saved earlier with localStorage.setItem("settings", JSON.stringify(obj))
const settings = parseJSON(localStorage.getItem("settings"), { theme: "light" });In our test this returned the fallback for a missing key, for the string "[object Object]" (what you get if you save an object without JSON.stringify) and for an object passed in directly, and the saved settings when they were valid. For JSON basics, see what JSON is and how to parse it.
Syntax Errors in Your Code
If the error is in your code, nothing in that file runs. These are the messages for the most common mistakes:
| Code | Chrome / Node.js | Safari | Fix |
|---|---|---|---|
| for (let i = 0; i < n; i++ { | Unexpected token '{' | Unexpected token '{'. Expected ')' to end a for-loop header. | Add the missing ) |
| console.log((1 + 2))); | Unexpected token ')' | Unexpected token ')' | Remove the extra ) |
| { first name: 'John' } | Unexpected identifier 'name' | Expected a ':' following the property name 'first'. | Quote it: { "first name": 'John' } |
| { age = 30 } | Invalid shorthand property initializer | Unexpected token '='. Expected a ':' … | Use a colon: { age: 30 } |
| ['red'; 'blue'] | Unexpected token ';' | Expected either a closing ']' or a ','… | Separate items with commas |
| "She said, "Hello!"" | Unexpected identifier 'Hello' | Unexpected identifier 'Hello'… | Use 'She said, "Hello!"' or escape: \" |
| “Hello” (curly quotes) | Invalid or unexpected token | Invalid character '\u201c' | Retype the quotes as " or ' |
| const add = (a, b) { … } | Unexpected token '{' | Unexpected token '{'… | Add the arrow: (a, b) => { … } |
| await inside a normal function | await is only valid in async functions and the top level bodies of modules | Unexpected identifier 'fetch'… | Mark the function async |
| if (x) { … }; else { … } | Unexpected token 'else' | Unexpected keyword 'else' | Remove the ; before else |
| function greet(name: string) | Unexpected token ':' | Unexpected token ':'… | Type annotations need TypeScript (.ts) |
Curly quotes usually come from copying code out of Word, Google Docs, chat apps or some websites. The old Chrome wording for this was Unexpected token ILLEGAL; current versions say Invalid or unexpected token.
The error line isn't always the mistake
JavaScript reports where it got confused, which can be well after the real mistake. Here the closing brace of the function is missing:
function calculateTotal(items) {
let total = 0;
for (const item of items) {
total += item.price;
}
return total;
// ← the function's closing } is missing here
console.log(calculateTotal([{ price: 5 }]));$ node --check total.js
total.js:10
SyntaxError: Unexpected end of inputNode.js points at line 10, past the end of the file, because it kept looking for the }. node --check file.js checks a file's syntax without running it, which is a quick way to find these. In the browser, click the file name next to the error in the console to jump to the line, then look above it for an unclosed bracket, brace or quote.
Errors That Look Like Syntax Problems but Aren't
A missing semicolon before ( or [
let total = 0
(function () {
console.log("IIFE");
})();
// TypeError: 0 is not a function
// JavaScript reads it as: let total = 0(function () { ... })();This is a TypeError at runtime, not a syntax error: without the semicolon, the next line's parentheses are read as a function call on 0. End the line with ;, or start lines that begin with (, [ or a backtick with a semicolon if you don't use semicolons.
Windows paths in strings
const a = "C:\Users\John\Documents"; // no error, but the value is "C:UsersJohnDocuments"
const b = "C:\users\john"; // SyntaxError: Invalid Unicode escape sequence
// Fixes
const c = "C:\\Users\\John\\Documents"; // escape each backslash
const d = String.raw`C:\Users\John\Documents`;
const e = "C:/Users/John/Documents"; // forward slashes work in Node.js on Windows tooA backslash starts an escape sequence. Most letters after it are silently dropped, which gives you a wrong path with no error. \u must be followed by a Unicode code, so a lowercase \users is a syntax error.
ESLint: "Parsing error: Unexpected token"
ESLint uses its own parser, which by default understands plain JavaScript only. JSX or TypeScript then fails with errors like these (ESLint 10.12):
src/Title.jsx
2:10 error Parsing error: Unexpected token <
src/greet.ts
1:27 error Parsing error: Unexpected token :Enable JSX for .js/.jsx files and use the TypeScript parser for .ts/.tsx files in eslint.config.js:
// eslint.config.js
import tseslint from "typescript-eslint";
export default [
{
files: ["**/*.{js,jsx}"],
languageOptions: {
parserOptions: { ecmaFeatures: { jsx: true } },
},
},
{
files: ["**/*.{ts,tsx}"],
languageOptions: { parser: tseslint.parser },
},
];npm install --save-dev typescript-eslint typescript@6With this config, both files above passed. One thing we hit while testing: in October 2026 the newest TypeScript is 7.0, and typescript-eslint 8.71 stops with typescript-eslint does not support TS 7.0, so install TypeScript 6 for linting until that changes. See the typescript-eslint docs and ESLint's language options for the full setup. If you use a framework's ESLint config (Next.js, Vite templates), it already sets this up for its file types.
Prevention Checklist
- Let your editor and a formatter like Prettier run on save: if formatting suddenly fails, there's a syntax error, and it tells you where.
- Run ESLint in your editor so mistakes are underlined as you type.
- Never call res.json() without checking res.ok; use a helper like getJSON.
- Build JSON with JSON.stringify() instead of by hand, and wrap JSON.parse() of outside data in try/catch.
- Paste code from documents into a plain-text editor first, or retype the quotes.
MDN has reference pages for Unexpected token and JSON.parse errors.
FAQs
What does "Uncaught SyntaxError: Unexpected token" mean?
The JavaScript engine found a character that isn't allowed at that point. "Uncaught" just means nothing caught the error. If it mentions JSON, the data is the problem; otherwise it's the code at or before the reported line.
How do I fix Unexpected token '<' in JSON at position 0?
Your code received HTML instead of JSON. Open the request in the browser's Network tab to see what came back (usually a 404 page, an index.html fallback or a login page), fix the URL or server, and check res.ok and the Content-Type before parsing.
What is "Unexpected end of JSON input"?
The string given to JSON.parse or res.json() was empty or incomplete, often a 204 response or a server error with no body.
Why does Safari say "The string did not match the expected pattern"?
That's Safari's message when res.json() can't parse the response. Treat it like Chrome's JSON errors: log await res.text() to see what the server really sent.
What does "Invalid or unexpected token" mean?
There's a character JavaScript can't use there at all, most often curly quotes (“ ”) or an invisible character pasted from a document. Retype that part of the line.
How do I fix ESLint "Parsing error: Unexpected token"?
Tell ESLint how to parse your files: enable JSX in parserOptions.ecmaFeatures for JSX, and use typescript-eslint's parser for TypeScript, as shown above.
Why is the error on the wrong line?
The engine reports where it noticed the problem. A missing } or ) is often only noticed lines later, even at the end of the file, so check the code just above the reported line.
Related Articles
View allAbout Muhaymin Bin Mehmood
Front-end Developer skilled in the MERN stack, experienced in web and mobile development. Proficient in React.js, Node.js, and Express.js, with a focus on client interactions, sales support, and high-performance applications.




