WordPress Enqueue Scripts Styles: The Right Way to Load JS and CSS
The right way to load assets in WordPress is through the wordpress enqueue scripts styles system — a built-in queue that manages what loads, when it loads, and in what order. Hardcoding <script> tags in your theme’s header.php is the #1 rookie mistake that causes dependency conflicts, duplicate loads, and sites that break when a plugin touches jQuery. Don’t do it. The wordpress enqueue scripts styles system is how every professional WordPress developer handles assets.
⚡ Key Takeaways
- Always use
wp_enqueue_script()andwp_enqueue_style()— never hardcode assets in template files. - The
wp_enqueue_scriptshook handles both scripts and styles on the front end. - Register first, enqueue later — the two-step pattern gives you maximum control.
- Use the dependencies array to let WordPress sort out load order automatically.
- WordPress 6.3+ supports
deferandasyncnatively via the strategy parameter. - Conditional loading keeps your pages lean — don’t load a slider script on every post.
- Cache-bust with
filemtime()so updated assets don’t get stuck behind stale caches. - You can dequeue any script or style — including bloated plugin assets you don’t need.
- The wordpress enqueue scripts styles system is required by WordPress.org for theme and plugin approval.
Why the WordPress Enqueue Scripts Styles System Exists
Before WordPress had a proper asset queue, theme developers just dropped <script src="..."> tags wherever they felt like it. That worked fine until plugins arrived, and suddenly three different plugins were loading three different versions of jQuery. Pages exploded. Users cried. It was a mess.
The wordpress enqueue scripts styles system was built to solve that. It’s a centralized registry. Every script and style that wants to load on a page has to register itself with a unique handle. If two things try to load the same handle, WordPress loads it once and moves on.
This system also manages dependencies. Tell WordPress your script depends on jQuery, and it will make sure jQuery loads first — every single time, without you thinking about it. That’s the deal. You use the queue, WordPress handles the order.

The Core Functions: wp_enqueue_script() and wp_enqueue_style()
These two functions are the heart of the wordpress enqueue scripts styles system. Learn their parameters cold — you’ll use them constantly.
wp_enqueue_script() Parameters
Here’s the full signature:
wp_enqueue_script(
string $handle, // Unique name for this script
string $src, // URL to the file
array $deps, // Array of dependency handles
string|bool $ver, // Version string (or false/null)
array|bool $args // Footer flag OR args array (WP 6.3+)
);
A real-world example for a theme:
wp_enqueue_script(
'my-theme-main',
get_template_directory_uri() . '/assets/js/main.js',
array( 'jquery' ),
'1.4.2',
true // Load in footer
);
The $handle is what the whole dependency system pivots on. Make it unique — prefix it with your theme or plugin slug. my-theme-main is safe. main is asking for a collision.
wp_enqueue_style() Parameters
Same pattern, slightly different signature:
wp_enqueue_style(
string $handle, // Unique name
string $src, // URL to the CSS file
array $deps, // CSS dependencies (rare but valid)
string|bool $ver, // Version string
string $media // 'all', 'print', 'screen', etc.
);
And in practice:
wp_enqueue_style(
'my-theme-style',
get_stylesheet_uri(),
array(),
wp_get_theme()->get( 'Version' )
);
Both functions are documented in full at the WordPress Developer Docs. Bookmark it. Seriously.
The wp_enqueue_scripts Hook — Where It All Connects
Here’s a thing that trips people up: the action hook you use to enqueue both scripts and styles on the front end is called wp_enqueue_scripts — plural, and despite the name, it’s for styles too. WordPress naming, don’t overthink it.
add_action( 'wp_enqueue_scripts', 'my_theme_assets' );
function my_theme_assets() {
wp_enqueue_style(
'my-theme-style',
get_stylesheet_uri(),
array(),
'2.1.0'
);
wp_enqueue_script(
'my-theme-main',
get_template_directory_uri() . '/assets/js/main.js',
array( 'jquery' ),
'2.1.0',
true
);
}
This is how the wordpress enqueue scripts styles system is meant to be used. You hook into wp_enqueue_scripts, and inside that callback you call your enqueue functions. Clean, predictable, correct.
If you’re building a WordPress child theme, this is exactly how you add your child theme’s assets without blowing up the parent theme.
🏴☠️ PIRATE TIP: Never put wp_enqueue_script() or wp_enqueue_style() calls loose in your functions.php outside of a hook. It might work today. It’ll blow up tomorrow when load order changes. Always hook into wp_enqueue_scripts like a professional.
Register vs Enqueue — The Two-Step Process
WordPress gives you two levels of control: registering an asset and enqueueing it. Most tutorials skip over this distinction, which is a shame because it’s actually powerful.
Registering tells WordPress an asset exists, what its URL is, and what it depends on — but doesn’t actually load it yet. Enqueueing says “load this on the current request.”
// Register the script (doesn't load it)
wp_register_script(
'my-slider',
get_template_directory_uri() . '/assets/js/slider.js',
array( 'jquery' ),
'1.0.0',
true
);
// Later — only enqueue it on pages that need it
if ( is_front_page() ) {
wp_enqueue_script( 'my-slider' );
}
The best use case for this pattern in the wordpress enqueue scripts styles system: plugin developers who register assets in a central location and let other parts of the codebase (or even other plugins) enqueue them on demand. It’s cleaner architecture for any project that gets beyond trivial complexity.
If you just call wp_enqueue_script() directly with a src URL, WordPress registers and enqueues it in one shot. That’s fine for most theme work. But if you’re building something modular, learn the two-step.
The Dependencies System — Let WordPress Handle Load Order
This is one of the best features in the wordpress enqueue scripts styles system and probably the least appreciated. Understanding how wordpress enqueue scripts styles dependencies work will save you hours of debugging. You declare what your script depends on, and WordPress figures out the order. Done.
wp_enqueue_script(
'my-accordion',
get_template_directory_uri() . '/assets/js/accordion.js',
array( 'jquery', 'jquery-ui-core' ),
'1.0.0',
true
);
WordPress sees that my-accordion needs jquery and jquery-ui-core, so it loads jQuery first, then jQuery UI Core, then your script. You never have to think about it again.
This also prevents duplicate loading. If five scripts all depend on jQuery, jQuery still only loads once. This is the entire reason the enqueue system was built, and it works beautifully.
200+
Built-in script handles registered by WordPress core — from jQuery to React to the block editor’s wp-element
Source: WordPress Developer Docs / wp-includes/script-loader.php
Footer vs Header Loading and the WordPress 6.3 Strategy Parameter
Loading position matters when you wordpress enqueue scripts styles. For years, the fifth parameter of wp_enqueue_script() was a simple boolean: true loads in the footer, false (or nothing) loads in the header. Loading in the footer is almost always what you want — it lets the page render before scripts block it.
WordPress 6.3 changed the game by replacing that boolean with a full $args array that supports a strategy key. Now you can do this:
wp_enqueue_script(
'my-theme-main',
get_template_directory_uri() . '/assets/js/main.js',
array( 'jquery' ),
'2.0.0',
array(
'strategy' => 'defer', // or 'async'
'in_footer' => true,
)
);
defer means the script downloads in parallel but executes after the HTML is parsed — great for scripts that manipulate the DOM. async means download in parallel and execute immediately when ready — suitable for analytics or scripts that don’t depend on the DOM. For most theme scripts, defer is the right call.
Combining this with the wordpress enqueue scripts styles system gives you serious page performance wins without any third-party plugin. Check out our full guide on how to speed up your WordPress site for more context on why this matters.
“Loading scripts in the header without a strategy parameter is voluntarily making your site slower. There’s no excuse for it in 2024.”— WordPress performance community consensus
Conditional Loading — Stop Loading Everything Everywhere
This is where most WordPress developers leave serious performance on the table when they wordpress enqueue scripts styles. The wordpress enqueue scripts styles system fully supports conditional logic — use it.
add_action( 'wp_enqueue_scripts', 'my_theme_conditional_assets' );
function my_theme_conditional_assets() {
// Only load contact form assets on the Contact page
if ( is_page( 'contact' ) ) {
wp_enqueue_script(
'my-contact-form',
get_template_directory_uri() . '/assets/js/contact.js',
array( 'jquery' ),
'1.0.0',
true
);
}
// Only load WooCommerce customizations on shop/product pages
if ( function_exists( 'is_woocommerce' ) && is_woocommerce() ) {
wp_enqueue_style(
'my-woo-styles',
get_template_directory_uri() . '/assets/css/woocommerce.css',
array(),
'1.0.0'
);
}
}
Common conditional tags: is_page(), is_single(), is_singular(), is_archive(), is_front_page(), is_home(). You can also check post IDs, page templates, and taxonomy terms. The wordpress enqueue scripts styles system doesn’t restrict you — you have the full WordPress conditional tag library at your disposal.

Passing PHP Data to JavaScript: wp_localize_script() and wp_add_inline_script()
At some point you’ll need to pass a PHP variable into JavaScript — an AJAX URL, a nonce, a user setting. There are two ways to do it in the wordpress enqueue scripts styles system, and they’re not interchangeable.
wp_localize_script()
The original method. It outputs a JSON object as an inline <script> block immediately before your enqueued script:
wp_enqueue_script(
'my-ajax-script',
get_template_directory_uri() . '/assets/js/ajax.js',
array( 'jquery' ),
'1.0.0',
true
);
wp_localize_script(
'my-ajax-script', // Must match enqueue handle
'myAjaxData', // JS variable name
array(
'ajaxUrl' => admin_url( 'admin-ajax.php' ),
'nonce' => wp_create_nonce( 'my_ajax_nonce' ),
)
);
In your JS file, you’d access this as myAjaxData.ajaxUrl and myAjaxData.nonce. It’s clean, it works, and it’s been around since forever. The name is confusing (it was originally for localization strings) but don’t let that bother you.
wp_add_inline_script()
The newer, more flexible option. Instead of outputting a named object, you output raw JavaScript before or after a registered script:
wp_add_inline_script(
'my-theme-main',
'const themeConfig = ' . json_encode( array(
'ajaxUrl' => admin_url( 'admin-ajax.php' ),
'version' => '2.0',
) ) . ';',
'before' // 'before' or 'after'
);
Use wp_add_inline_script() when you need to run code that isn’t a simple variable assignment, or when you want control over placement. It’s more powerful than wp_localize_script() for complex cases. Both are fully supported parts of the wordpress enqueue scripts styles system.
admin_enqueue_scripts — Handling Backend Assets
Everything above covers how to wordpress enqueue scripts styles on the front end. For the WordPress admin, you use a different hook: admin_enqueue_scripts. Same functions, different hook — and you get a free parameter telling you which admin screen you’re on.
add_action( 'admin_enqueue_scripts', 'my_plugin_admin_assets' );
function my_plugin_admin_assets( $hook_suffix ) {
// Only load on our plugin's settings page
if ( 'settings_page_my-plugin' !== $hook_suffix ) {
return;
}
wp_enqueue_style(
'my-plugin-admin-style',
plugin_dir_url( __FILE__ ) . 'assets/admin.css',
array(),
'1.0.0'
);
wp_enqueue_script(
'my-plugin-admin-script',
plugin_dir_url( __FILE__ ) . 'assets/admin.js',
array( 'jquery' ),
'1.0.0',
true
);
}
The $hook_suffix variable is gold. Common values: post.php, post-new.php, edit.php, upload.php. For custom admin pages, the hook suffix follows the format {page_type}_page_{menu_slug}. Use it to avoid loading admin assets on every single admin screen — the same performance logic applies here as on the front end.
Dequeue and Deregister — Removing Unwanted Assets
Once you understand how to wordpress enqueue scripts styles, you also need to know how to remove them. Not all assets you need to manage are ones you’re adding. Sometimes a plugin loads a bloated CSS file you’re completely overriding, or jQuery UI styles that conflict with your theme. The wordpress enqueue scripts styles system lets you remove them too.
add_action( 'wp_enqueue_scripts', 'my_theme_remove_assets', 20 );
function my_theme_remove_assets() {
// Dequeue (removes from load queue, keeps registered)
wp_dequeue_style( 'contact-form-7' );
// Deregister (removes it completely)
wp_deregister_style( 'contact-form-7' );
// Remove a script
wp_dequeue_script( 'some-plugin-slider' );
}
Critical detail: set your hook priority to 20 or higher. The plugin adds its assets at priority 10 (default). If you try to dequeue at priority 10, you might run before the plugin does — and there’s nothing to dequeue yet. Higher priority number = runs later in the queue.
There’s an important difference between dequeue and deregister. Dequeue removes it from the load list for this request but keeps the registration. Deregister wipes it from the registry completely, which means even if something later tries to enqueue it by handle, it won’t load. For troublesome plugin styles you never want, deregister is the nuclear option.
🏴☠️ PIRATE TIP: Before you deregister a plugin’s stylesheet, make sure nothing else depends on it. Check the $deps array of other scripts. Blowing up a dependency silently is one of the nastier bugs to track down — learn how at our guide on how to debug WordPress.
Cache Busting With filemtime() and Common Mistakes to Avoid
Version strings in the wordpress enqueue scripts styles system aren’t just documentation. Proper cache busting is a critical part of how you wordpress enqueue scripts styles in production. WordPress appends them as query strings (?ver=1.0.0) which browsers and CDNs use for caching. If you set a static version and update the file, visitors get the stale cached version. Cache busting fixes that.
Cache Busting With filemtime()
wp_enqueue_script(
'my-theme-main',
get_template_directory_uri() . '/assets/js/main.js',
array( 'jquery' ),
filemtime( get_template_directory() . '/assets/js/main.js' ),
true
);
filemtime() returns the file’s last modified timestamp. Every time you save the file, the timestamp changes, the query string changes, and caches are busted. Don’t use this in production if your files change frequently — use it in development and switch to a real version string for releases.
The Most Common Mistakes
Hardcoding scripts in header.php. We already covered this. It bypasses the entire wordpress enqueue scripts styles dependency system. Stop it.
Loading assets on every page. That contact form JS running on your blog posts is pure waste. Conditional loading exists for a reason.
Forgetting dependencies. If your script needs jQuery and you leave the deps array empty, it’ll fail unpredictably based on load order. Always declare dependencies — that’s the whole point of the system.
jQuery noConflict confusion. When you wordpress enqueue scripts styles that depend on jQuery, WordPress loads jQuery in noConflict mode by default, which means the $ shortcut is not available. Either use jQuery directly, wrap your code in (function($) { ... })(jQuery);, or switch to vanilla JS. This is one of the most Googled WordPress JS problems and 100% preventable.
Using the wrong hook. Calling enqueue functions on init instead of wp_enqueue_scripts is a common mistake, especially for developers coming from other frameworks. It might work, but it’s wrong — you lose conditional tag support and other context. Understanding WordPress hooks, actions, and filters properly prevents this class of errors entirely.
WordPress Built-In Scripts You Should Know
WordPress ships with a massive library of pre-registered scripts. Use their handles in your dependencies array instead of loading your own copies. The full reference is in the WordPress Theme Handbook.
The ones you’ll encounter most often: jquery, jquery-ui-core, jquery-ui-sortable, jquery-ui-dialog, underscore, backbone, wp-util, wp-api-fetch, wp-element (React), wp-components, wp-blocks, wp-i18n. If you’re building Gutenberg blocks, that last group is your whole world.
The main thing to understand about the wordpress enqueue scripts styles system and built-ins: WordPress deduplicates by handle. If your theme and a plugin both depend on jquery, jQuery loads exactly once. This is why handles must be unique for your own code but shared for core dependencies — it’s the mechanism that prevents the multi-jQuery chaos of the pre-enqueue era. Mastering how to wordpress enqueue scripts styles with built-in handles is what separates amateurs from professionals.
Also worth knowing: you can also manage this properly in a wp-config.php context for things like forcing SCRIPT_DEBUG to load non-minified versions. Setting define( 'SCRIPT_DEBUG', true ); in wp-config makes WordPress load the non-minified versions of all core assets — essential for debugging. And if you’re just adding simple custom CSS, the guide on adding custom CSS to WordPress covers the lighter-weight options too.
⚔️ Pirate Verdict
Every WordPress developer needs to understand how to wordpress enqueue scripts styles properly. The wordpress enqueue scripts styles system isn’t complicated — it’s a queue with handles, dependencies, and hooks. Master those three concepts and you’ll never hardcode an asset again. The developers who skip this system are the ones spending Friday nights debugging why their site broke after a plugin update. Use wp_enqueue_script() and wp_enqueue_style() for everything. Hook into wp_enqueue_scripts. Declare your dependencies. Load conditionally. Cache-bust with filemtime() in dev. The system does the heavy lifting — you just have to use it right. Check the Arsenal for our full list of tools that play nicely with proper asset management.
Frequently Asked Questions About WordPress Enqueue Scripts Styles
What is the difference between wp_register_script() and wp_enqueue_script()?
wp_register_script() tells WordPress that a script exists — its URL, version, and dependencies — but doesn’t load it. wp_enqueue_script() adds it to the actual load queue for the current request. If you call wp_enqueue_script() with a full src URL, it registers and enqueues in one step. The two-step pattern (register then enqueue separately) is most useful when you want to define an asset once and load it conditionally from multiple places in your codebase.
How do I add JavaScript to a specific page only in WordPress?
Use WordPress conditional tags inside your wp_enqueue_scripts callback. Wrap your wp_enqueue_script() call in an if ( is_page( 'contact' ) ) check, or use is_single(), is_archive(), or any other conditional tag. This is a core feature of the wordpress enqueue scripts styles system — there’s no need for a plugin to handle this. You can also check by page ID, template name, or post type.
Why are my scripts loading in the wrong order?
Almost always because you didn’t declare dependencies. If your script needs jQuery, add 'jquery' to the $deps array when you enqueue. WordPress uses that dependency graph to determine output order. Another common cause: trying to dequeue or enqueue at the same priority as another callback that hasn’t run yet. Try bumping your hook priority up to 20 or higher so it runs after other things have been registered.
How do I remove a plugin’s CSS or JavaScript?
Use wp_dequeue_style() or wp_dequeue_script() with the asset’s handle, hooked into wp_enqueue_scripts at priority 20 or later. You need to know the handle — find it by viewing the page source and looking at the id attribute on the link or script tag (WordPress appends -css or -js to the handle). If you want to prevent it from loading at all (including blocking other things from enqueueing it by handle), use wp_deregister_style() or wp_deregister_script() instead.
Should I use jQuery or vanilla JavaScript in WordPress?
Vanilla JavaScript is the right answer for new projects in 2024. jQuery was essential when browser compatibility was a nightmare — it isn’t anymore. Modern browsers handle DOM manipulation, fetch, and event listeners natively. The wordpress enqueue scripts styles system still ships jQuery, and it’s fine to depend on it for legacy work, but shipping a new theme in 2024 with jQuery as a hard dependency is unnecessary weight. Write vanilla JS, load it with wp_enqueue_script() with no jQuery dependency, and your users will thank you with faster page loads.
How do I pass PHP variables to JavaScript in WordPress?
Use wp_localize_script() for simple object data — call it after your wp_enqueue_script() call with the same handle, a JS variable name, and an array of data. For more complex inline code, use wp_add_inline_script() which lets you output raw JavaScript before or after your registered script. Both are proper parts of the wordpress enqueue scripts styles system and both handle escaping correctly when you use json_encode() for complex values. Never use PHP echo to manually inject JavaScript into template files — that’s the old bad way.