The “javascript $ is not defined” error shows $ isn’t available in that script, so you need to load jQuery first or stop using $.
You’ll usually see this error the moment a page loads, right when a script runs. It can happen on a tiny landing page or inside a big app build. The good news is that the fix is rarely mysterious. In most cases, either jQuery never loaded, it loaded after your code, or another library grabbed the $ symbol.
This guide walks you through quick checks, clean fixes, and a few tricky cases like bundlers, WordPress themes, and mixed libraries. You’ll get a tight checklist you can run in order, plus examples you can paste in with confidence.
What JavaScript $ Is Not Defined Usually Means
The $ symbol is not a built-in JavaScript feature. It becomes available only when some script defines it. Most of the time that script is jQuery, where $ is an alias for jQuery.
So when the browser says $ is not defined, it’s telling you: “I hit a line that uses $, and at that moment, no variable named $ exists in this scope.” That can be because jQuery is missing, blocked, loaded late, or replaced.
One fast way to confirm the cause is the browser console. Open DevTools, then type typeof window.jQuery and press Enter. If you get "undefined", jQuery isn’t present. If you get "function", jQuery exists, and the issue is timing, scope, or a conflict with $.
Fast Causes That Trigger The Error
This error comes from a small set of root causes. Nail the right one, and you’ll fix it once instead of chasing it across files.
jQuery Is Not Loaded At All
This is the classic case. The page references your script that uses $, but it never includes jQuery. It can happen after a refactor, a theme change, or when someone deletes a CDN tag without noticing what depends on it.
jQuery Loads After Your Script
Load order matters. If your file runs before jQuery is parsed, $ won’t exist yet. This often shows up when scripts are moved to the footer, when defer or async is added, or when a bundler splits chunks and changes execution order.
A Different Library Took Over $
Some libraries use $ too. If you load one of those after jQuery, $ may point somewhere else. In some setups, a library will remove $ on purpose to avoid collisions. Your code keeps calling $, and then it breaks.
Your Code Runs Before The DOM Is Ready
Sometimes jQuery exists, yet selectors fail because the elements aren’t in the page yet. That usually throws a different error, yet it can look similar when scripts are wrapped in modules and the timing gets messy. A proper DOM-ready wrapper removes that uncertainty.
The Script Tag Points To The Wrong File
Typos in a URL, a moved file, a missing build output, or a blocked request can stop jQuery from arriving. Check the Network tab and look for 404s, blocked requests, or red lines on the jQuery file.
Fix Checklist You Can Run In Order
Start with the checks that take seconds, then move to the deeper ones. This order saves time and keeps you from making “fixes” that only hide the real issue.
- Confirm jQuery exists — In DevTools Console, run
typeof window.jQueryandtypeof window.$to see what’s present. - Check the script load order — In your HTML, make sure the jQuery script tag appears before the script that calls
$. - Inspect the Network tab — Reload the page, filter by “jquery”, and verify the file returns 200 and isn’t blocked.
- Remove async from jQuery — If jQuery has
async, it may execute after your code. Drop it for libraries with dependents. - Use a DOM-ready wrapper — Wrap your jQuery code so it runs after the document is ready.
- Hunt for conflicts — If another library uses
$, switch tojQueryor usenoConflict(). - Validate bundler imports — In module builds, import jQuery explicitly and expose it only when needed.
Load jQuery Before Your Script
On a plain HTML page, the clean fix is simple: load jQuery first, then your script. Keep both in the same part of the page so the order stays obvious during edits.
Wrap Your Code So It Runs At The Right Time
If your code touches elements on the page, add a DOM-ready wrapper. This also helps when markup is injected late by templates.
jQuery(function ($) {
// $ is safe inside this function
$(".menu-toggle").on("click", function () {
$(".menu").toggleClass("open");
});
});
Use The Right Attribute For Script Tags
defer keeps execution order among deferred scripts, while async does not. If you mark jQuery as async, your dependent file may run first and crash. If you want non-blocking loading, defer both scripts and keep jQuery first.
Table Of Symptoms, Causes, And Fixes
If you’re scanning a bug report or a client message, this table helps you jump straight to the most likely fix.
| What You Notice | Likely Cause | What To Do |
|---|---|---|
| Error on first load, jQuery is undefined | jQuery missing or blocked | Add jQuery, fix the URL, or serve it locally |
| Error appears only sometimes | async timing race | Remove async, use defer, keep order stable |
| window.jQuery exists, window.$ is undefined | $ released by noConflict | Use jQuery() or pass $ into a wrapper |
| Works on one page, fails on another | Different template loads scripts | Move shared scripts into the base layout |
| Only fails after adding another library | $ collision | Use jQuery or isolate $ inside a function |
Fixing $ Conflicts With noConflict And Mixed Libraries
When two libraries both want $, you can keep jQuery and still avoid collisions. The tool here is jQuery.noConflict(), which gives up $ so another library can use it.
The safest pattern is to avoid relying on a global $ at all. Use a wrapper and pass $ in as a local alias. That keeps your code readable and prevents surprise breakage when other scripts arrive.
- Release the global $ — Call
jQuery.noConflict()after jQuery loads, before other scripts that depend on a different $. - Use a local alias — Wrap your code and accept $ as a parameter so it still reads like jQuery.
- Switch to jQuery when needed — In shared files, calling
jQuery()is clear and avoids ambiguity.
var jq = jQuery.noConflict();
jq(function () {
jq(".tab").on("click", function () {
jq(".tab").removeClass("active");
jq(this).addClass("active");
});
});
If you’re working inside a CMS or theme that already runs noConflict(), the global $ may be missing by design. That’s common on WordPress, where themes often expect you to use jQuery or a wrapper that passes in $.
Bundlers, Modules, And Build Pipelines
Modern builds change how variables reach the page. When you move from script tags to bundlers, $ might be scoped inside a module and never become global. Your old inline script still calls $, and it fails.
Import jQuery Explicitly In Your Entry File
If your code uses jQuery, import it. Then decide whether you want a global alias. Many teams avoid globals and keep jQuery local to modules. If you still need global access for legacy scripts, set it once in your entry file.
// entry.js
import jQuery from "jquery";
window.jQuery = jQuery;
window.$ = jQuery;
Watch For Code Splitting And Lazy Chunks
Some builds load pages in chunks. A chunk that uses jQuery can run before the chunk that exposes jQuery globally. If you see the error only on certain routes, check the chunk graph and move the jQuery setup into the earliest entry that runs on every route.
Check ESLint And TypeScript Settings
Linters can warn you before runtime. If you use TypeScript with jQuery types, add the proper type package and avoid sprinkling declare var $ as a band-aid. Declarations can silence the editor while the page still crashes for users.
WordPress And Theme Cases That Bite People
WordPress loads scripts through its enqueue system. If you hard-code a jQuery CDN tag in a theme, you can end up with two copies of jQuery, mismatched versions, or odd ordering. That’s when the same code works on one page and fails on another.
Load scripts through the enqueue API and declare jQuery as a dependency. That lets WordPress place scripts in a safe order and avoids duplicated libraries.
- Enqueue your script — Register your file with WordPress and load it on the pages that need it.
- Declare jQuery as a dependency — Add
array("jquery")so your script waits for jQuery. - Wrap code in a jQuery function — WordPress runs jQuery in noConflict mode, so pass $ in locally.
function theme_scripts() {
wp_enqueue_script(
"theme-app",
get_template_directory_uri() . "/js/app.js",
array("jquery"),
null,
true
);
}
add_action("wp_enqueue_scripts", "theme_scripts");
If you still see “javascript $ is not defined” on WordPress after enqueueing, scan for inline scripts in widgets or page builders. Those often run outside the safe wrapper. Move that code into your enqueued file, or wrap it with jQuery(function($){ ... }).
Preventing The Error Before It Reaches Users
Once you fix the immediate bug, it’s worth adding guardrails so it doesn’t pop back up after the next edit. A few small habits catch most regressions.
- Keep dependencies close — Put library script tags near the scripts that rely on them, or keep the ordering managed by your build tool.
- Fail fast in dev — Run pages with DevTools open and treat console errors as release blockers.
- Use one source of jQuery — Mixing CDN and local copies invites version drift and hard-to-trace timing issues.
- Prefer scoped imports — In module code, import jQuery where you use it instead of leaning on globals.
- Test slow networks — Throttle to “Slow 3G” and reload; timing races show up fast when requests lag.
- Remove dead inline scripts — Inline snippets in templates or editors break easily during changes and are tough to audit.
Caches can trick you during testing. If a CDN serves an older HTML file that still loads your script before jQuery, you’ll see the bug even after you “fixed” it. Hard refresh, clear the service worker cache if you use one, and verify the final HTML that the browser received in the Network tab on mobile. If your build outputs hashed filenames, confirm the page points to the new hash, not yesterday’s file.
If you’re deciding whether to keep jQuery at all, ask one simple question: do you still need it for a plugin or legacy feature? If not, swapping small jQuery snippets for modern DOM methods can cut page weight and remove the whole $ class of errors. If you do keep it, the fixes above make it stable and predictable.
When you hit this error again, run the checklist, confirm load order, and check whether $ got claimed by another script. That routine beats guesswork every time.
