jQuery is already there
WordPress includes its own copy of jQuery, and the admin area, many themes and many plugins depend on it. WordPress upgrades that copy on its own schedule, so check which version a site runs with jQuery.fn.jquery in the browser console before relying on jQuery 4 behaviour.
The golden rule: do not add a second copy. Loading another jQuery from a CDN in your theme's header can break plugins that attached themselves to the first one, and wastes bandwidth.
Loading your script properly
In a theme's functions.php (or a plugin), register your script with wp_enqueue_script and list jquery as a dependency:
<?php
add_action('wp_enqueue_scripts', function () {
wp_enqueue_script(
'mytheme-site', // a unique handle
get_template_directory_uri() . '/js/site.js', // the file
['jquery'], // load WordPress's jQuery first
'1.0.0', // version, for cache busting
['in_footer' => true] // load before </body>
);
});WordPress then prints jQuery once, followed by your file, in the right order, on every front-end page. Use the admin_enqueue_scripts hook for scripts in the admin area.
noConflict mode: where is $?
WordPress loads jQuery in noConflict mode, so the short name $ is not jQuery, because other libraries may want it. Code copied from a normal tutorial fails:
<p class="note">Hello</p>
<script src="https://code.jquery.com/jquery-4.0.0.min.js"></script>
<script>jQuery.noConflict();</script>
<script>
$(".note").text("Changed"); // what most tutorials show
</script>Hello
TypeError: $ is not a functionjQuery.noConflict() gives $ back to whatever owned it before jQuery loaded; here, nothing. The full name jQuery still works everywhere.
Getting $ back safely
You do not have to type jQuery all the time. Two patterns give you a private $ that means jQuery:
<p class="note">Hello</p>
<script src="https://code.jquery.com/jquery-4.0.0.min.js"></script>
<script>jQuery.noConflict();</script>
<script>
// 1. Document ready that receives $ (the most common in WordPress themes)
jQuery(function ($) {
$(".note").text("Changed with $ inside the ready function");
console.log("ready wrapper:", $(".note").text());
});
// 2. An immediately invoked function, for code that does not need to wait
(function ($) {
console.log("IIFE wrapper: $ is jQuery?", $ === jQuery, "| version", $.fn.jquery);
})(jQuery);
console.log("outside, $ is", typeof $);
</script>IIFE wrapper: $ is jQuery? true | version 4.0.0
outside, $ is undefined
ready wrapper: Changed with $ inside the ready functionInside each wrapper, $ is a parameter that receives jQuery, so it does not matter what $ means elsewhere. Wrap all of your theme's jQuery code in one of these.
Living with another library
noConflict exists because jQuery is not the only library that likes $. Here an older library has already taken $ before jQuery loads:
<script>
// Pretend another library defined $ first
window.$ = function () { return "other library"; };
</script>
<script src="https://code.jquery.com/jquery-4.0.0.min.js"></script>
<script>
console.log("after loading jQuery, $ is jQuery?", $ === jQuery);
const jq = jQuery.noConflict();
console.log("after noConflict, $() returns:", $());
console.log("jQuery still works as jq: version", jq.fn.jquery);
</script>after loading jQuery, $ is jQuery? true
after noConflict, $() returns: other library
jQuery still works as jq: version 4.0.0noConflict() returned the other library's $ and handed back jQuery itself, which you can store under any name you like.
Passing data from PHP
Scripts often need values that only PHP knows: the Ajax URL, a security nonce, translated text. Do not echo PHP values into a script tag by hand; attach them to your script with WordPress's functions, which escape them properly:
<?php
wp_enqueue_script('mytheme-site', get_template_directory_uri() . '/js/site.js', ['jquery'], '1.0.0', true);
wp_add_inline_script('mytheme-site', 'const mythemeSettings = ' . wp_json_encode([
'ajaxUrl' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('mytheme_like'),
'labels' => ['liked' => __('Liked!', 'mytheme')],
]) . ';', 'before');Then use it from your jQuery code:
jQuery(function ($) {
$(".like-button").on("click", function () {
const $btn = $(this);
$.post(mythemeSettings.ajaxUrl, {
action: "mytheme_like",
nonce: mythemeSettings.nonce,
post_id: $btn.data("postId")
}).done(() => $btn.text(mythemeSettings.labels.liked));
});
});On the PHP side, the wp_ajax_mytheme_like hook handles the request, and it must check the nonce with check_ajax_referer('mytheme_like', 'nonce') before doing anything. The older wp_localize_script() function is also widely used for passing data this way. The PHP course covers the server side of handling requests safely.
Tips for WordPress sites
- Use the WordPress copy of jQuery, never deregister it to load another version unless you have tested every plugin with that version.
- Load scripts in the footer and only on the pages that need them (check
is_page(),is_singular()and similar before enqueueing). - Use delegation for content that page builders and plugins add after load.
- Prefix everything: handles, global variables, custom events and CSS classes, so your theme never clashes with a plugin.
Common mistakes
- Adding jQuery from a CDN in
header.phpalongside WordPress's copy. - Using
$outside a wrapper, which fails in noConflict mode. - Printing PHP values into JavaScript by hand, which breaks on quotes and can open XSS holes.
- Skipping the nonce check on Ajax handlers.
Exercise
Write theme code that, on document ready, adds the class has-js to the html element and logs the number of links in the main menu (#primary-menu a). It must work in noConflict mode.
Show one possible answer
<ul id="primary-menu"><li><a href="/">Home</a></li><li><a href="/blog/">Blog</a></li></ul>
<script src="https://code.jquery.com/jquery-4.0.0.min.js"></script>
<script>jQuery.noConflict();</script>
<script>
jQuery(function ($) {
$("html").addClass("has-js");
console.log($("#primary-menu a").length, "menu links");
});
</script>2 menu linksKey points
- WordPress ships its own copy of jQuery; load it with wp_enqueue_script and a 'jquery' dependency instead of adding another copy.
- WordPress runs jQuery in noConflict mode, so $ is not jQuery by default; use jQuery or a wrapper that receives $.
- jQuery(function ($) { ... }) runs on document ready and gives you a safe $ inside.
- Pass data from PHP with wp_add_inline_script or wp_localize_script, and escape it; never print PHP values into JavaScript by hand.