Uncaught ReferenceError: X is not defined

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

Chrome DevTools console showing a red error row reading Uncaught ReferenceError: apiKey is not defined, sourced to app.js line 3.
Captured from Chrome 152. ReferenceError, not 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:

MessageWhat it meansWhere to look
x is not definedThe name resolves in no scope at allLoad order, typo, wrong environment
Cannot access 'x' before initializationThe name exists but is in the temporal dead zoneA 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 callableExport 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

MessageCauseFix
$ is not definedjQuery script not loaded / loaded latereorder or fix the script tag
require is not definedCommonJS code shipped to the browserbundle it, or use import
process is not definedNode global used in client codeuse the bundler's client env API
a var from another scriptmodule scope isn't sharedexport/import explicitly
works locally, not in proda script failed to load (404/CSP)check the Network tab for that request

Debugging checklist

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

More JavaScript & runtime errors

Browse the full reference for JavaScript, Node.js, and database errors — exact message, cause, and fix.

All Error References Cannot access 'X' before initialization HTTP Status Codes
About the author

Pasindu Ishan is a Senior Software Engineer working on data streaming systems, which means most days end at a JSON payload that will not parse. The JavaScript references here are checked against a real runtime before being written up. He builds the free in-browser tools at JSON Dev Tools.