Quick answer
JavaScript searched every scope up to global and found no variable named X anywhere. Unlike "cannot read properties," this isn't about a value being empty — X was never declared where this code can see it. The usual causes: a typo/case mismatch, a script that hasn't loaded yet (or at all), or code assuming a Node global (require, process) that doesn't exist in the browser.
The exact error string
console.log(totalPrice);
// Uncaught ReferenceError: totalPrice is not defined
$(".menu").toggle();
// Uncaught ReferenceError: $ is not defined
// -> jQuery's <script> tag never loaded, or loads after this line
What it actually looks like
TypeError — the engine could not resolve the name in any scope, so nothing was even evaluated. When the name does resolve but holds the wrong kind of value you get a TypeError instead.How to diagnose it in 30 seconds
First check spelling and case — JavaScript is case-sensitive, so userName and username are different identifiers. If the name looks right, the identifier is either never declared, or declared somewhere this code can't reach: a script that loads later, a different module's top-level scope, or a block scope ({ }) that ended before this line runs.
Cause 1: script load order
<!-- ❌ your code runs before jQuery has loaded -->
<script src="app.js"></script>
<script src="https://code.jquery.com/jquery.min.js"></script>
<!-- ✅ dependency first -->
<script src="https://code.jquery.com/jquery.min.js"></script>
<script src="app.js"></script>
Cause 2: the script failed to load at all
Open the Network tab: a 404, a blocked request (ad blocker, CSP), or a typo'd src path all mean the file that would have declared X never ran. The reference error shows up wherever X is later used, not at the broken <script> tag itself.
Cause 3: Node globals in browser code
const fs = require("fs");
// ❌ Uncaught ReferenceError: require is not defined (require is Node-only)
console.log(process.env.API_URL);
// ❌ Uncaught ReferenceError: process is not defined (process is Node-only)
// ✅ in a bundled app, use ESM import syntax and the bundler's env mechanism:
import { readFile } from "some-browser-safe-lib";
console.log(import.meta.env.VITE_API_URL); // Vite's client-exposed env
Cause 4: module scope isn't shared
<!-- config.js -->
<script type="module">
const apiUrl = "https://api.example.com";
</script>
<!-- app.js -->
<script type="module">
console.log(apiUrl);
// ❌ apiUrl is not defined — each module has its own top-level scope
</script>
<!-- ✅ export it explicitly and import where needed -->
<!-- config.js --> export const apiUrl = "https://api.example.com";
<!-- app.js --> import { apiUrl } from "./config.js";
Three ReferenceErrors that are not the same bug
Before you go looking for a missing script tag, check which message you actually have. All three are ReferenceError, and they mean different things:
| Message | What it means | Where to look |
|---|---|---|
x is not defined | The name resolves in no scope at all | Load order, typo, wrong environment |
Cannot access 'x' before initialization | The name exists but is in the temporal dead zone | A let/const/class declared lower down, or a circular import |
x is not a function (a TypeError) | The name resolved fine; the value is not callable | Export shape, not load order |
The distinction is worth the ten seconds. Is not defined is the only one of the three where "the script did not load" or "this is the wrong runtime" is a plausible explanation.
Probing safely with typeof — and its one exception
typeof is the only operator that can inspect a name that may not exist without throwing. That makes it the correct feature-detection tool for globals that exist in one environment but not another:
if (typeof window !== "undefined") { /* browser-only code */ }
if (typeof process !== "undefined") { /* Node-only code */ }
console.log(typeof neverDeclared); // ✅ "undefined" — no error
The exception, verified on Node v24.15.0, is the temporal dead zone: typeof on a let or const that is declared later in the same scope throws ReferenceError: Cannot access 'x' before initialization rather than returning "undefined". So the guard is safe for globals you may not have, and unsafe for your own bindings further down the file.
For environment-specific globals there is now a better answer than guarding at all: globalThis is defined in every modern runtime, so globalThis.fetch or globalThis.crypto reads as a plain property access — and a missing property is simply undefined, never a ReferenceError.
Common variants at a glance
| Message | Cause | Fix |
|---|---|---|
$ is not defined | jQuery script not loaded / loaded late | reorder or fix the script tag |
require is not defined | CommonJS code shipped to the browser | bundle it, or use import |
process is not defined | Node global used in client code | use the bundler's client env API |
| a var from another script | module scope isn't shared | export/import explicitly |
| works locally, not in prod | a script failed to load (404/CSP) | check the Network tab for that request |
Debugging checklist
- ✓ Check spelling/case — JavaScript identifiers are case-sensitive
- ✓ Confirm the declaring script/import runs, and runs before, this line
- ✓ Check the Network tab — did that script actually load (200, not 404)?
- ✓ Using
require/processin browser code? Those are Node-only - ✓ Split across
<script type="module">tags? Export/import explicitly - ✓ TypeScript flags most of this at compile time before you ever run the code
Frequently Asked Questions
What does 'X is not defined' mean?
JavaScript looked up the identifier X through every enclosing scope, all the way to the global scope, and found no variable, function, or class by that name declared anywhere reachable. This is different from a value being null or undefined — here the name itself was never declared in any scope the engine could see.
Why does a library like jQuery say it's not defined?
The <script> tag that loads the library either wasn't included, is loaded after your code runs, or failed to load (404, blocked, wrong path). Check the Network tab for the script's request, and make sure library <script> tags appear before the code that uses the global they define.
Why does 'require is not defined' happen in the browser?
require is a Node.js/CommonJS global — browsers don't have it natively. This happens when server-side (CommonJS) code is shipped straight to the browser without bundling, or an ES module file accidentally uses require instead of import. Use a bundler (webpack/Vite/esbuild) or switch to import syntax.
Why does 'process is not defined' happen outside Node?
process is a Node.js global; it doesn't exist in the browser. This is common when frontend code reads process.env.X the way server code does — in bundlers like Vite, use their client-exposed env mechanism (e.g. import.meta.env) instead of assuming process is available.
Why is a variable from another <script> tag not defined?
Each ES module has its own scope — a top-level variable in one <script type="module"> is not automatically visible in another. Classic (non-module) scripts do share the global scope, so mixing module and non-module scripts is a common source of this. Export/import explicitly, or attach the value to window if you need a true global.
How do I fix 'X is not defined' fast?
Check spelling and case first (JavaScript is case-sensitive). Then confirm the declaring script/import actually runs before this line, and that it isn't scoped somewhere this code can't see (a different module, a block scope with let/const, or a script that failed to load).
References
- ReferenceError: "x" is not defined (MDN)
- Grammar and types (MDN)
More JavaScript & runtime errors
Browse the full reference for JavaScript, Node.js, and database errors — exact message, cause, and fix.