wp_enqueue_*
wp_enqueue_script and wp_enqueue_style are the proper way to load assets in WordPress. They register, version, and order assets, avoid double-loading, and integrate with conditional loading, dependencies, and the block editor.
wp_enqueue_script, deps, localize
EXAMPLE
<?php
// 1) Where to hook
// • Frontend assets: 'wp_enqueue_scripts'
// • Admin assets: 'admin_enqueue_scripts'
// • Block editor: 'enqueue_block_editor_assets'
// • Login page: 'login_enqueue_scripts'
// 2) Frontend example
add_action( 'wp_enqueue_scripts', function () {
$theme = wp_get_theme();
// CSS
wp_enqueue_style(
'mytheme-main', // handle
get_template_directory_uri() . '/assets/main.css', // src
array(), // dependencies
$theme->get( 'Version' ), // version (cache-buster)
'all' // media
);
// JS — load in footer
wp_enqueue_script(
'mytheme-app',
get_template_directory_uri() . '/assets/app.js',
array( 'wp-element' ), // depends on wp.element
$theme->get( 'Version' ),
array( 'in_footer' => true, 'strategy' => 'defer' ) // WP 6.3+ args array
);
} );
// 3) Why dependencies matter
// • WordPress generates <script> tags in dependency order — your code can rely on what it needs
// • Built-in handles: 'jquery', 'wp-element' (React), 'wp-i18n', 'wp-api-fetch', 'wp-data', 'wp-blocks', etc.
// • If two plugins enqueue the same handle, WP de-duplicates automatically
// 4) Pass data from PHP -> JS
add_action( 'wp_enqueue_scripts', function () {
wp_enqueue_script(
'mytheme-app',
get_template_directory_uri() . '/assets/app.js',
array(), '1.0.0', array( 'in_footer' => true )
);
wp_localize_script(
'mytheme-app',
'MyThemeData',
array(
'restUrl' => esc_url_raw( rest_url() ),
'nonce' => wp_create_nonce( 'wp_rest' ),
'isLoggedIn' => is_user_logged_in(),
'siteUrl' => home_url(),
)
);
} );
// In app.js
fetch( MyThemeData.restUrl + 'wp/v2/posts', {
headers: { 'X-WP-Nonce': MyThemeData.nonce },
} );
// 5) Newer alternative — wp_add_inline_script with safer JSON
add_action( 'wp_enqueue_scripts', function () {
wp_enqueue_script( 'mytheme-app', '/assets/app.js', array(), '1.0', true );
wp_add_inline_script(
'mytheme-app',
'window.MyThemeData = ' . wp_json_encode( array(
'restUrl' => rest_url(),
'nonce' => wp_create_nonce( 'wp_rest' ),
) ) . ';',
'before'
);
} );
// 6) Conditional loading — only on the pages that need it
add_action( 'wp_enqueue_scripts', function () {
if ( is_singular( 'product' ) ) {
wp_enqueue_script(
'mytheme-product',
get_template_directory_uri() . '/assets/product.js',
array(), '1.0.0', true
);
}
if ( is_front_page() ) {
wp_enqueue_style( 'mytheme-hero', '/assets/hero.css', array(), '1.0' );
}
} );
// 7) Deregister + replace a script (e.g. swap jQuery)
add_action( 'wp_enqueue_scripts', function () {
if ( ! is_admin() ) {
wp_deregister_script( 'jquery' );
wp_register_script(
'jquery',
'https://code.jquery.com/jquery-3.7.1.slim.min.js',
array(), '3.7.1', true
);
}
}, 1 ); // priority 1 — before themes/plugins enqueue
// 8) Async / defer / inline
// WP 6.3+ — pass an args array on wp_enqueue_script:
wp_enqueue_script(
'analytics',
'https://example.com/analytics.js',
array(),
null, // null disables ?ver= for third parties
array( 'in_footer' => true, 'strategy' => 'async' )
);
// Older approach — script_loader_tag filter
add_filter( 'script_loader_tag', function ( $tag, $handle ) {
if ( $handle === 'analytics' ) {
return str_replace( '<script ', '<script async ', $tag );
}
return $tag;
}, 10, 2 );
// 9) Versioning + cache busting
// • Always pass a real version — WordPress appends ?ver= so browsers re-fetch on change
// • For dynamically built assets, use filemtime() so a rebuild auto-busts the cache:
$src = get_template_directory_uri() . '/dist/app.js';
$path = get_template_directory() . '/dist/app.js';
$version = file_exists( $path ) ? filemtime( $path ) : '1.0.0';
wp_enqueue_script( 'mytheme-app', $src, array(), $version, true );
// 10) Translations — pair with wp_set_script_translations
add_action( 'wp_enqueue_scripts', function () {
wp_enqueue_script(
'mytheme-app',
get_template_directory_uri() . '/assets/app.js',
array( 'wp-i18n' ), '1.0', true
);
wp_set_script_translations( 'mytheme-app', 'mytheme', get_template_directory() . '/languages' );
} );
// In app.js
import { __ } from '@wordpress/i18n';
console.log( __( 'Hello', 'mytheme' ) );
// 11) Block editor assets
add_action( 'enqueue_block_editor_assets', function () {
wp_enqueue_script(
'mytheme-block-editor',
get_template_directory_uri() . '/build/editor.js',
array( 'wp-blocks', 'wp-element', 'wp-i18n', 'wp-data' ),
'1.0', true
);
wp_enqueue_style(
'mytheme-block-editor',
get_template_directory_uri() . '/build/editor.css',
array( 'wp-edit-blocks' ),
'1.0'
);
} );
// 12) Admin assets — only on pages you target
add_action( 'admin_enqueue_scripts', function ( $hook ) {
if ( $hook !== 'toplevel_page_mytheme-settings' ) return;
wp_enqueue_script( 'mytheme-admin', '/assets/admin.js', array( 'wp-element' ), '1.0', true );
wp_enqueue_style ( 'mytheme-admin', '/assets/admin.css', array(), '1.0' );
} );
// 13) Preloading critical assets
add_action( 'wp_head', function () {
$src = get_template_directory_uri() . '/assets/hero.jpg';
echo '<link rel="preload" as="image" href="' . esc_url( $src ) . '" fetchpriority="high">';
}, 1 );
// 14) Asset files (modern builds) — wp-scripts generates an .asset.php sidecar
// build/app.asset.php → array( 'dependencies' => array(...), 'version' => '...' )
$asset = include get_template_directory() . '/build/app.asset.php';
wp_enqueue_script(
'mytheme-app',
get_template_directory_uri() . '/build/app.js',
$asset['dependencies'],
$asset['version'],
true
);
// 15) Common bugs
// • Inlining <script src="…"></script> in template files → bypasses dependencies, loads order is random
// • Missing dependency → 'jQuery is not defined' errors
// • Forgot 'in_footer' → blocking render
// • wp_localize_script BEFORE the script is registered → silently no-op
// • Setting version to '1.0.0' forever → browsers cache forever; use filemtime()
// • Loading every asset on every page → use is_singular / is_page / is_front_page conditions
// • Hard-coded URLs (http://) → mixed-content errors on HTTPS sites; use get_*_uri() helpers
// • Using deregister inside the same handler that registers — wrong priority order
Why it matters
Enqueue everything through wp_enqueue_script / wp_enqueue_style so WordPress can manage dependencies, ordering, deduplication, and translations. Pass real version numbers (use filemtime() for built assets), put scripts in the footer with defer by default, and conditionally enqueue only on the pages that need them.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
add_action('wp_enqueue_scripts', function () {
wp_enqueue_style('main', get_stylesheet_uri());
wp_enqueue_script('app', get_template_directory_uri() . '/js/app.js', [], '1.0', true);
});
Try it Yourself »
Exercise
Load a stylesheet on the front end.
('site', $url);
wp_enqueue_style.
Discussion
Loading…