Why enqueue?
You could put <link rel="stylesheet"> and <script> tags straight into a template. In WordPress that causes problems: two plugins might load the same library twice, scripts might load before what they depend on, and nothing could remove or replace them. Instead, WordPress keeps a list of assets. You register and enqueue your files, with their dependencies, and WordPress prints each one once, in the right order and the right place.
This lesson continues the Bakery theme from Your first block theme.
Enqueueing a stylesheet and a script
Add some CSS and JavaScript files to the theme:
.site-notice { background: #fbf7f0; padding: 0.5rem 1rem; }document.addEventListener( 'DOMContentLoaded', () => {
console.log( 'Bakery notice script loaded for', window.bakerySettings?.shopName );
} );Then enqueue them in functions.php, on the wp_enqueue_scripts hook:
<?php
add_action( 'wp_enqueue_scripts', function () {
$version = wp_get_theme()->get( 'Version' );
wp_enqueue_style(
'bakery-style', // a unique handle
get_theme_file_uri( 'assets/css/bakery.css' ), // URL of the file
[], // dependencies
$version // version, for cache busting
);
wp_enqueue_script(
'bakery-notice',
get_theme_file_uri( 'assets/js/notice.js' ),
[],
$version,
[ 'strategy' => 'defer', 'in_footer' => true ]
);
wp_add_inline_script( 'bakery-notice', 'window.bakerySettings = ' . wp_json_encode( [ 'shopName' => get_bloginfo( 'name' ) ] ) . ';', 'before' );
} );The arguments:
- a handle: a unique name, prefixed with your theme or plugin name to avoid clashes;
- the file's URL:
get_theme_file_uri()finds it in the theme (or a child theme's copy); - dependencies: handles that must load first;
- a version: added as
?ver=1.0.0, so when you change the version, browsers fetch the new file instead of a cached copy; - for scripts, where and how to load:
in_footerputs the tag before</body>, andstrategyaddsdeferorasync.
Here is what WordPress printed in the page:
GET /<link rel='stylesheet' id='bakery-style-css' href='http://localhost:8080/wp-content/themes/bakery/assets/css/bakery.css?ver=1.0.0' media='all' />
<script id="bakery-notice-js-before">
window.bakerySettings = {"shopName":"My WordPress Site"};
//# sourceURL=bakery-notice-js-before
<script data-wp-strategy="defer" defer id="bakery-notice-js" src="http://localhost:8080/wp-content/themes/bakery/assets/js/notice.js?ver=1.0.0">The stylesheet is in the head with its version. The script has defer and comes right after a small inline script that sets window.bakerySettings. That is how you pass data from PHP to JavaScript, with wp_json_encode() making the values safe. WordPress adds a //# sourceURL= comment to inline scripts so that browser developer tools can show them by name.
Dependencies and libraries
WordPress includes many libraries, registered under handles you can depend on: jquery, wp-api-fetch, wp-i18n, wp-element (React) and more. Depending on a handle makes WordPress load it once, before your script, even if several plugins need it:
<?php
wp_enqueue_script( 'bakery-menu', get_theme_file_uri( 'assets/js/menu.js' ), [ 'jquery' ], '1.0.0', true );
global $wp_scripts;
echo "jquery is registered as: ", implode( ', ', $wp_scripts->registered['jquery']->deps ), "\n";
echo "jquery-core version: ", $wp_scripts->registered['jquery-core']->ver, "\n";
echo "will print, in order: ";
$wp_scripts->all_deps( [ 'bakery-menu' ] );
echo implode( ' > ', $wp_scripts->to_do ), "\n";jquery is registered as: jquery-core, jquery-migrate
jquery-core version: 3.7.1
will print, in order: jquery-core > jquery-migrate > jquery > bakery-menujquery is an alias that loads jquery-core and jquery-migrate; your script prints after them. Check the version WordPress bundles before relying on features of a specific jQuery release (the jQuery course covers the differences between versions 3 and 4).
Load only where needed
Every asset costs download time, so load it only where it is used. Conditional tags tell you what kind of page is being shown:
add_action( 'wp_enqueue_scripts', function () {
if ( is_singular( 'post' ) ) {
wp_enqueue_script( 'bakery-share', get_theme_file_uri( 'assets/js/share.js' ), [], '1.0.0', [ 'strategy' => 'defer' ] );
}
if ( is_page( 'contact' ) ) {
wp_enqueue_style( 'bakery-map', get_theme_file_uri( 'assets/css/map.css' ), [], '1.0.0' );
}
} );Block themes have a better tool for block-specific CSS: wp_enqueue_block_style( 'core/quote', [ ... ] ) loads a stylesheet only on pages that actually contain that block.
Styles in the editor
Front-end CSS does not automatically apply inside the block editor. To make the editor look like the site, add the stylesheet to the editor too:
add_action( 'after_setup_theme', function () {
add_editor_style( 'assets/css/bakery.css' );
} );For styles that only the editor needs, use the enqueue_block_editor_assets hook, and for the admin area use admin_enqueue_scripts.
Removing someone else's asset
Because everything goes through the queue, you can also remove an asset a plugin adds where you do not need it:
add_action( 'wp_enqueue_scripts', function () {
if ( ! is_page( 'contact' ) ) {
wp_dequeue_style( 'some-form-plugin-style' );
}
}, 100 ); // priority 100: run after the plugin enqueued itFind the handle in the id attribute of the tag: WordPress prints id="handle-css" for styles and id="handle-js" for scripts.
Common mistakes
- Hard-coding tags in templates or
header.php. - Loading a second copy of jQuery from a CDN instead of depending on
jquery. - Forgetting the version, so visitors keep an old cached file after you change it.
- Generic handles such as
mainorscript, which clash with other plugins. - Loading everything everywhere.
Exercise
Enqueue a stylesheet assets/css/print.css that is used only when printing (hint: the fifth argument of wp_enqueue_style() is the media type), and check the <link> tag in the page source.
Show one possible answer
add_action( 'wp_enqueue_scripts', function () {
wp_enqueue_style( 'bakery-print', get_theme_file_uri( 'assets/css/print.css' ), [], '1.0.0', 'print' );
} );The page source contains <link rel='stylesheet' id='bakery-print-css' href='.../assets/css/print.css?ver=1.0.0' media='print' />. The browser downloads it without blocking the page and only applies it when printing.
Key points
- Never hard-code <link> or <script> tags in templates; enqueue them on the wp_enqueue_scripts hook.
- wp_enqueue_style() and wp_enqueue_script() take a handle, a URL, dependencies and a version used for cache busting.
- For scripts, set in_footer or a strategy (defer or async) so they do not block the page from appearing.
- Load assets only where they are used, with conditional tags such as is_singular(), and pass data to scripts with wp_add_inline_script().