You load static assets in WordPress by using the enqueue system, not by hard-coding HTML tags. It’s designed to make the process more robust, namely:
- It allows for specifying dependencies and automatically loads them in the correct order.
- It loads a given asset only once, even if you enqueue it multiple times.
The system is based on handles. Every file you enqueue has to have a handle. It’s a unique identifier for that file. WordPress will then check if it already has this handle in the queue, and if it has, it won’t load it again.
Beware not to load the same file twice under different handles. WordPress does not check the URL of the file, only the handle. Look out for loading different files under the same handle as well. WordPress will only load the first file enqueued with a given handle. That’s why you should make sure the handles of your theme’s files are unique by using the theme slug, like ‘theme-slug-handle’.
Read more about including assets in the official WordPress Including Assets Documentation.
Loading Styles
You enqueue styles by calling this function:
wp_enqueue_style($handle, $src = '', $deps = array(), $ver = false, $media = 'all');It registers the file with URL $src, dependencies $deps, and version $ver as having a handle $handle ($media is for specifying the <link>’s HTML ‘media’ attribute, e.g., screen, print, etc.). This is basically a shorthand for registering the file with wp_register_style() first and then enqueuing it with wp_enqueue_style( $handle ).
You never call this function directly. Instead, you call it inside another wrapper function, and then register this function with one of these hooks:
- wp_enqueue_scripts – for loading assets on the frontend. Beware the confusing name, it’s for both scripts and styles.
- admin_enqueue_scripts – for loading assets in the admin panel (/wp-admin/).
- login_enqueue_scripts – for loading assets on the login page (wp-login.php).
- enqueue_block_editor_assets – for loading assets only within the block editor’s iframe.
- enqueue_block_assets – for loading assets on the block editor screen and the frontend.
- And other more specialized ones.
This ensures the file is added to the queue when the code responsible for this system is already in memory. The files’ HTML tags will then be printed in the correct place on the rendered page (in the <head>).
The final code snippet for loading a css/main.css file might look like this (you would put it inside functions.php):
function my_theme_assets() {
wp_enqueue_style(
'my-theme-style', // The handle
get_theme_file_uri( 'css/main.css' ), // The URL
array( 'example-dependency-handle' ), // Dependencies
filemtime( get_theme_file_path( 'css/main.css' ) ) // Auto versioning trick
);
}
add_action( 'wp_enqueue_scripts', 'my_theme_assets' );You can also load inline CSS with wp_add_inline_style().
Loading Block Stylesheets
You can enqueue CSS files for specific blocks. To do that, you have to call the wp_enqueue_block_style() function. This function expects the block’s name and an array of arguments (almost the same as arguments to wp_enqueue_style()).
The CSS will then be included on the frontend and in the editor whenever the block is used. You should only use this function as a last resort – when styling the block with theme.json is impossible or unmanageable. Read the official Block Stylesheets documentation if you need this functionality.
Loading Fonts
The easiest way to load fonts is using a font delivery service like Google Fonts. You just enqueue the URL they give you and you’re done. But you shouldn’t do it. Please self-host your fonts. It’s faster, more robust, and most importantly, it’s GDPR compliant (Google Fonts is not).
Fonts are loaded in CSS files, so the process for enqueueing them is exactly the same as for styles. You just need to create the file first if you’re self-hosting. There are tools for that, like google-webfonts-helper.
Loading Scripts
The idea for loading JavaScript files is the same as for CSS files, but you use wp_enqueue_script() instead. With scripts, you also have the choice to load them either in the <head> or the <footer> of the page. To load inline js, you can use wp_add_inline_script().
Passing Data From PHP to JS
Sometimes your JavaScript needs some data that is available only on the backend. You could create an API endpoint to serve this data, but it’s like using a sledgehammer to crack a nut.
The official and correct way to include data available in PHP for use in JavaScript is by using the wp_add_inline_script() function. You would write your js in php and “attach” it before an enqueued script (the one you need the data in). Keep in mind the timing of execution. If you enqueue the script in the head, but the data you need is not available at that point, then you won’t be able to get it. In that case, you’d have to enqueue the script in the footer.
There’s also an older way, which you will probably see way more often. It’s using wp_localize_script(). This function takes a php array as an argument and renders an inline script with the equivalent JavaScript object or array. The original purpose of this function was exactly what it’s named – localization (translations). The modern best practice is to load data using wp_add_inline_script(). This helps keep the code more semantically correct, which makes it more readable and self-documenting.
Loading Assets Conditionally
Unfortunately, including assets conditionally in WordPress kind of sucks. The way you do it is different depending on the context. There are 2 ways.
Template-level logic is when you need to include an asset for a specific template or page. You’d use WordPress Conditional Tags for that. Let’s say you have a CSS file that is only used in your single.php template. You would enclose your wp_enqueue_style call in an if (is_single()) conditional tag. But what if you only need a style for a certain page? That’s where the sucking happens. You would be forced to check either the slug or the ID of the page, both of which are brittle as they can change and are decoupled from the code of the actual page.
Component-level logic is when you need to include an asset for a specific component. This is way nicer as there are no conditional checks involved. The wp_enqueue_script() is called in the code of the component itself, which means it’s automatically included on every page the component is used on. The component’s code is run in the contents of the page, which means it’s called after the <head> has already been rendered. This limits the use of this technique to scripts loaded in the footer only, as styles can only be included in the head.
Update: My tests have shown that WordPress renders every asset left in the queue in wp_footer(). That means if you enqueue any script or style after wp_head() has run (e.g., in your php templates), it will be rendered in the <body>. This is confusing and quirky behavior, and can lead to unplanned script executions (if you set $in_footer = false thinking it won’t be rendered) or invalid HTML (if you enqueue a style, which will render a <link> in the <body>).
The way you’d typically use component-level logic is by calling wp_enqueue_script() in your shortcode’s code or the render_callback function of a dynamic block (if you implement your component as a block).
Getting File URLs
As you might’ve noticed in the code snippet above, you shouldn’t hard-code your assets’ URLs. If you do, good luck with domain name migrations. You should use one of these 3 functions:
- get_stylesheet_uri() – returns the active theme’s style.css URL (child theme’s style.css if it’s active).
- get_theme_file_uri( $file ) – returns the active theme’s URL, with an optional $file parameter. Falls back to the parent theme if a child theme is active and the file doesn’t exist.
- get_parent_theme_file_uri( $file ) – same as the previous one, but this one searches the parent directory only.
The get_theme_file_uri() and get_parent_theme_file_uri() functions were introduced in WordPress 4.7. The way to include assets before that was by using either get_template_directory_uri() or get_stylesheet_directory_uri(). These functions return the URL to the parent or the active theme directory. That means you had to use string concatenation to get the file URL.
Most importantly, the get_stylesheet_directory_uri() does not fall back to the parent directory if the file doesn’t exist in the child directory (like get_theme_file_uri() does). That means you can’t easily overwrite an asset if the parent theme uses these functions. They are legacy functions. Do not use them (unless you know what you’re doing).
Scripts Bundled With WordPress
WordPress bundles many custom and third-party scripts (e.g., jQuery). You should always use those bundled scripts to avoid loading the same script twice, thus creating conflicts with plugins. The full list of bundled scripts is available in the /wp-includes/script-loader.php file.