BugMojoBugMojoBugMojo
FeaturesPricingBlogHelpAbout
Add to ChromeLog inGet started
BugMojoBugMojo

Bug reports that actually help fix bugs — capture, replay, share.

A product of Softech Infra.

Product

  • Features
  • Pricing
  • Browser extension
  • Get started
  • Log in

Resources

  • Help & guides
  • Blog
  • Compare
  • Glossary

Company

  • About
  • Contact
  • Security
  • Privacy
  • Terms
  • Sitemap
© 2026 BugMojo. All rights reserved.
AllGuidesEngineeringPlaybooksCompareGlossaryAlternativesBy roleBug tracking by framework
  1. Home
  2. Blog
  3. Guides
  4. How to Fix "ReferenceError: x is not defined"
Guide

How to Fix "ReferenceError: x is not defined"

A ReferenceError means JavaScript looked up a name and found nothing at all. Here is how it differs from a TypeError, the causes worth checking first, and the fix that matches each one.

Hrishikesh BaidyaHrishikesh Baidya·Jul 16, 2026·9 min read
Guides
Isometric line-art browser console showing a ReferenceError that a variable is not defined, highlighted in red beside a lime fix, on a dark canvas
TL;DR
  • What it means: JavaScript looked up a name — a variable, function, or global — and found no declaration for it at all in the current scope.
  • Not a TypeError: a ReferenceError is about a name that does not exist; a TypeError is about a value that exists but is the wrong type or undefined.
  • Usual causes: a typo, using a variable before it is declared (the temporal dead zone), the name living in a different scope, a missing import or unloaded script, or a global from the wrong environment (process, require, window).
  • Fix it: correct the name, declare before use, check the scope, add the import or fix load order, or use the right environment's global.

ReferenceError: x is not defined is JavaScript being blunt with you: it tried to resolve the name x, walked the entire scope chain from where you used it out to the global object, and came up with nothing. There is no variable, no function, no import, no global by that name in reach. The engine cannot even guess what you meant, so it stops.

That is genuinely useful information, because it narrows the problem to one question — why can this name not be found here? — and there are only a handful of answers. This guide walks each of them, with a broken snippet and the fix beside it.

What the error actually means

A ReferenceError is thrown when you reference a name that does not exist in the current scope chain — JavaScript resolved the identifier and found no binding for it. It is distinct from a TypeError, which fires when a name does resolve but its value is the wrong type. In short: 'is not defined' means the name is missing, not that a value is wrong.

This is the distinction that trips people up, so it is worth nailing down before the fixes. Both errors show up red in the same console, but they point at opposite problems:

  • ReferenceError — the name is missing. usrname was never declared, so there is nothing to read. JavaScript never even gets to a value.
  • TypeError — the name is fine, but the value is unusable. user.name where user is undefined, or calling something that is not a function. The lookup succeeded; what came back was wrong.

If you are staring at "cannot read properties of undefined" or "x is not a function," you are in TypeError territory and those guides are the right stop. If the message ends in is not defined, stay here.

The causes, from most to least common

Almost every ReferenceError is one of the following. Read the name in the message first — it is the single most important clue — then match it to the pattern below.

1. A simple typo

The most common cause is also the most boring: the name in the message is not spelled the way you declared it. Case matters, and so does every letter.

1-typo.jsjavascript
const userName = 'Ada';
console.log(username);
// ReferenceError: username is not defined

// Fix: match the declared name exactly (case-sensitive)
const userName = 'Ada';
console.log(userName);

When the message names something you are sure you declared, compare the two spellings character by character — username vs userName, fetchdata vs fetchData. An editor with rename-symbol refactoring and a linter's no-undef rule catch most of these before you ever run the code.

2. Using a variable before it is declared

This is the case that surprises people who learned that declarations are "hoisted." They are — but let and const hoist differently from var, and the difference is a whole class of ReferenceError.

2-temporal-dead-zone.jsjavascript
console.log(count); // ReferenceError: Cannot access 'count' before initialization
let count = 0;

// Compare with var, which does NOT throw here:
console.log(legacy); // undefined — no error
var legacy = 0;

// Fix: declare and initialize before you read it
let count = 0;
console.log(count); // 0

Here is what is actually happening. Both let and var bindings are hoisted to the top of their scope, so in a sense the name count exists from the first line of the block. The difference is initialization. A var is initialized to undefined immediately, so reading it early gives you undefined rather than an error. A let or const binding is not initialized until its declaration line executes. The window between the top of the block and that line is the temporal dead zone (TDZ): the name is reserved, but touching it throws Cannot access 'count' before initialization — a ReferenceError.

The TDZ exists on purpose. It turns a silent undefined bug into a loud, immediate error, which is almost always what you want. The fix is never to switch back to var; it is to move the declaration above the first use, which is where it belonged anyway.

The message wording is your tell. x is not defined means no binding exists at all — a typo, a missing import, a wrong scope. Cannot access 'x' before initialization means the binding does exist but you reached it inside its temporal dead zone. Same error class, two very different fixes.

3. The name lives in a different scope

A variable declared with let or const is scoped to the nearest block — the braces of an if, a loop, or a function. Try to read it from outside those braces and it is genuinely not defined there, even though it was moments ago on a nested line.

3-block-scope.jsjavascript
if (isAdmin) {
  const permissions = loadPermissions();
}
console.log(permissions);
// ReferenceError: permissions is not defined (it only existed inside the if-block)

// Fix: declare it in the scope where you need to read it
let permissions;
if (isAdmin) {
  permissions = loadPermissions();
}
console.log(permissions);

The same shape shows up across function and module boundaries: a helper reaches for a variable that was local to a different function, or a module uses a name that was never exported from — or imported into — it. If the name is defined somewhere but not here, the fix is to widen the declaration to a shared scope, pass it in as an argument, or export and import it explicitly.

4. A missing import or an unloaded script

When the missing name is a function or class from another file or a library, the cause is usually a forgotten import or a script that has not loaded yet. In a module system the fix is one line; with plain <script> tags it is about order.

4-missing-import.jsjavascript
// debounce lives in a library, but was never imported
const run = debounce(save, 300);
// ReferenceError: debounce is not defined

// Fix: import the name into this module
import { debounce } from 'lodash-es';
const run = debounce(save, 300);

With classic script tags there is no import statement, so a name is only available after the <script> that defines it has run. If your code references $ before the jQuery tag loads — or sits above it in the HTML — you get $ is not defined. Order the defining script first, or use defer / a module bundler so dependencies resolve before your code runs. If the name is a whole missing package rather than an unimported symbol, that is a different failure — see "Cannot find module."

5. A global from the wrong environment

Some names are not yours to declare — they are provided by the runtime. The catch is that Node.js and the browser provide different ones. Ask for a global the current environment never defined and you get a ReferenceError.

5-wrong-environment.jsjavascript
// Running in the browser (or a server component):
const key = process.env.API_KEY;
// ReferenceError: process is not defined — `process` is a Node global

// Running in Node:
document.querySelector('#app');
// ReferenceError: document is not defined — `document` is a browser global

// Fix (browser build): use the bundler's env, not Node's process
const key = import.meta.env.VITE_API_KEY;

// Fix (universal code): guard on the global before touching it
if (typeof window !== 'undefined') {
  document.querySelector('#app');
}

process, require, __dirname, and Buffer are Node globals; window, document, and localStorage are browser globals. Server-side rendering makes this especially common: a component runs on the Node server first, where window does not exist, then again in the browser, where it does. Guarding with typeof window !== 'undefined' — which, unlike a bare reference, never throws on an undeclared name — or moving the browser-only code into an effect that runs only on the client is the standard fix. For build-time values, use your bundler's mechanism (Vite's import.meta.env, a process polyfill, or a define plugin) rather than expecting Node's process to survive into the browser.

Working from the stack trace

The name in the message tells you what is missing; the stack trace tells you where. The top frame is the exact file, line, and column that referenced the missing name — start there, confirm the spelling, then decide which of the five causes above applies. In production that frame often points into a minified bundle, so enable source maps in your build (or your error tracker) to resolve it back to your real source.

Tip

A ReferenceError is frequently environment-specific: the same code that runs fine in your dev server throws process is not defined only in the production browser bundle, or window is not defined only during server render. That is exactly the kind of bug that is hard to reproduce from a stack trace alone. Capturing the session — the console output plus the environment it ran in — shows you whether the missing name is a load-order or wrong-environment problem specific to where it broke, instead of leaving you to guess.

Preventing them before they run

Most ReferenceErrors are catchable before the code ever executes. A linter with ESLint's no-undef rule flags a reference to any name that was never declared or imported — which covers typos, missing imports, and most scope mistakes in one pass. TypeScript goes further: it resolves every identifier against its declarations and its knowledge of which environment your file targets, so a browser global used in a Node file, or a name used before its declaration, becomes a compile error at your desk rather than a runtime crash for a user.

The one category tooling cannot fully save you from is the wrong-environment global in universal code, because whether window exists depends on where the module happens to run. There, a deliberate guard — or splitting the browser-only work into a client-only path — is the durable fix.

Key takeaway

The honest summary: is not defined means the name does not exist in this scope — full stop. Read the name, check it against the five causes (typo, temporal dead zone, wrong scope, missing import, wrong-environment global), and pick the fix that matches. Do not confuse it with a TypeError, which is about a value that exists but is wrong. When the message is Cannot access 'x' before initialization instead, you are in the temporal dead zone — move the declaration up.

⁓ ⁓ ⁓
See which environment the name went missing in

Install the free BugMojo extension and capture the exact session — console output and the environment it ran in — when the error fires, so you can tell a load-order or wrong-environment ReferenceError apart at a glance.

Install the free extension

Frequently asked questions

Frequently asked questions

Sources

  1. ReferenceError: "x" is not defined — MDN reference explaining the message and its common causes — MDN Web Docs (2026)
  2. ReferenceError — MDN reference: thrown when referencing a variable that does not exist (or is not yet initialized) — MDN Web Docs (2026)
  3. Hoisting — MDN glossary: how declarations are conceptually moved to the top of their scope, and how var and let/const differ — MDN Web Docs (2026)
  4. let — MDN reference: the temporal dead zone, where a hoisted binding exists but is not yet initialized — MDN Web Docs (2026)
Share:
Hrishikesh Baidya
Hrishikesh Baidya· Chief Technology Officer

Hrishikesh Baidya is the CTO at Softech Infra. He is drawn to architecture that is invisible — systems that simply work — and leads the engineering behind BugMojo.

On this page

  • What the error actually means
  • The causes, from most to least common
  • 1. A simple typo
  • 2. Using a variable before it is declared
  • 3. The name lives in a different scope
  • 4. A missing import or an unloaded script
  • 5. A global from the wrong environment
  • Working from the stack trace
  • Preventing them before they run

Get bug-tracking insights, weekly.

Engineering deep-dives, QA playbooks, and honest tool comparisons. No spam — unsubscribe in one click.