WordPress Plugin Asset Loading
Use this skill when adding or reviewing plugin JS/CSS enqueue code. The goal is to load the right asset on the right screen, with a correct dependency graph and modern loading hints.
This skill avoids Gutenberg-specific editor development. It covers general WordPress frontend/admin assets.
When to use this skill
Trigger when ANY of the following is true:
- Code calls
wp_enqueue_script(),wp_register_script(),wp_enqueue_style(),wp_script_add_data(),wp_style_add_data(),wp_register_script_module(), orwp_enqueue_script_module(). - A plugin loads assets on every admin page or every frontend request without checking context.
- The task mentions
defer,async,fetchpriority, script modules, module translations,module_dependencies, inline CSS, asset bloat, or frontend performance. - Code uses legacy IE conditional comments or
wp_style_add_data( $handle, 'conditional', ... ).
Runtime placement
| Context | Hook |
|---|---|
| Frontend scripts/styles | wp_enqueue_scripts |
| Admin scripts/styles | admin_enqueue_scripts |
| Login page assets | login_enqueue_scripts |
| Specific plugin settings page | Check $hook_suffix in admin_enqueue_scripts |
Do not enqueue admin assets globally unless the UI appears globally.
add_action( 'admin_enqueue_scripts', static function ( string $hook_suffix ): void {
if ( 'settings_page_myplugin' !== $hook_suffix ) {
return;
}
wp_enqueue_script(
'myplugin-admin',
plugins_url( 'assets/admin.js', MYPLUGIN_FILE ),
array( 'wp-api-fetch' ),
MYPLUGIN_VERSION,
array(
'in_footer' => true,
'strategy' => 'defer',
'fetchpriority' => 'low',
)
);
} );
Script loading args
Since WP 6.3, the fifth wp_enqueue_script() parameter can be an args array. Since WP 6.9, it also accepts fetchpriority.
Since WP 7.0, it also accepts module_dependencies so a classic script can dynamically import registered script modules.
wp_enqueue_script(
'myplugin-frontend',
plugins_url( 'assets/frontend.js', MYPLUGIN_FILE ),
array(),
MYPLUGIN_VERSION,
array(
'in_footer' => true,
'strategy' => 'defer',
'fetchpriority' => 'low', // 'auto', 'low', or 'high'.
)
);
Guidance:
- Use
in_footer => truefor non-critical frontend behavior. - Use
strategy => 'defer'for scripts that can run after parsing and preserve dependency order. - Use
strategy => 'async'only for independent scripts that do not depend on execution order. - Use
fetchpriority => 'high'rarely, only for scripts that are genuinely critical to initial rendering. - Use
fetchpriority => 'low'for behavior that should not compete with LCP resources. - If a classic script uses
module_dependencies, it must either setin_footer => trueorstrategy => 'defer'; otherwise it can run before the import map exists.
wp_enqueue_script(
'myplugin-admin',
plugins_url( 'assets/admin.js', MYPLUGIN_FILE ),
array( 'wp-api-fetch' ),
MYPLUGIN_VERSION,
array(
'in_footer' => true,
'module_dependencies' => array(
'@wordpress/abilities',
),
)
);
Script modules
For ES modules, use the Script Modules API on WP 6.5+:
wp_enqueue_script_module(
'myplugin/frontend',
plugins_url( 'assets/frontend.js', MYPLUGIN_FILE ),
array(),
MYPLUGIN_VERSION,
array(
'in_footer' => true,
'fetchpriority' => 'low',
)
);
In WP 6.9, wp_register_script_module() and wp_enqueue_script_module() accept an $args array with in_footer and fetchpriority. Feature-detect if supporting older WP:
if ( function_exists( 'wp_enqueue_script_module' ) ) {
wp_enqueue_script_module( 'myplugin/frontend', $src, array(), MYPLUGIN_VERSION );
} else {
wp_enqueue_script( 'myplugin-frontend', $fallback_src, array(), MYPLUGIN_VERSION, array( 'in_footer' => true ) );
}
In WP 7.0, registered script modules can have translations:
wp_register_script_module(
'myplugin/admin',
plugins_url( 'assets/admin.js', MYPLUGIN_FILE ),
array(),
MYPLUGIN_VERSION
);
wp_set_script_module_translations(
'myplugin/admin',
'myplugin',
plugin_dir_path( MYPLUGIN_FILE ) . 'languages'
);
wp_enqueue_script_module( 'myplugin/admin' );
Call wp_set_script_module_translations() after the module is registered. Use wp_set_script_translations() for classic scripts and wp_set_script_module_translations() for script modules.
Styles and legacy conditionals
WP 6.9 removed support for legacy conditional asset loading for Internet Explorer. Do not use wp_style_add_data( $handle, 'conditional', 'IE' ); in WP 6.9, a stylesheet with conditional data is ignored.
// WRONG on WP 6.9+.
wp_style_add_data( 'myplugin-ie', 'conditional', 'IE' );
// RIGHT - drop legacy IE-only styles, or serve a normal stylesheet if still required.
wp_enqueue_style( 'myplugin-admin', plugins_url( 'assets/admin.css', MYPLUGIN_FILE ), array(), MYPLUGIN_VERSION );
Use the path style data only when the stylesheet is registered and the file path is absolute:
wp_register_style( 'myplugin-small', plugins_url( 'assets/small.css', MYPLUGIN_FILE ), array(), MYPLUGIN_VERSION );
wp_style_add_data( 'myplugin-small', 'path', plugin_dir_path( MYPLUGIN_FILE ) . 'assets/small.css' );
wp_enqueue_style( 'myplugin-small' );
Inline data
Use wp_add_inline_script() for boot data and wp_set_script_translations() for translations. Do not use wp_localize_script() as a generic JSON dump.
wp_add_inline_script(
'myplugin-admin',
'window.mypluginSettings = ' . wp_json_encode( $settings ) . ';',
'before'
);
Critical rules
- Register/enqueue on the correct hook for frontend, admin, or login.
- Gate admin assets by screen using
$hook_suffixorget_current_screen(). - Use script args arrays, not the old boolean-only fifth parameter, when setting strategy/footer/fetchpriority.
- Do not use
asyncon dependency-sensitive scripts. - For classic scripts with
module_dependencies, use footer placement ordefer. - Use the matching translation API:
wp_set_script_translations()for classic scripts,wp_set_script_module_translations()for modules. - Do not use legacy IE
conditionaldata on styles in WP 6.9+. - Do not put
<script>tags insidewp_add_inline_script(). - Prefer dependencies over manual load ordering.
Common mistakes
// WRONG - loads everywhere in wp-admin.
add_action( 'admin_enqueue_scripts', static function (): void {
wp_enqueue_script( 'myplugin-admin', plugins_url( 'admin.js', __FILE__ ) );
} );
// RIGHT - load only where the screen exists.
add_action( 'admin_enqueue_scripts', static function ( string $hook_suffix ): void {
if ( 'settings_page_myplugin' !== $hook_suffix ) {
return;
}
wp_enqueue_script(
'myplugin-admin',
plugins_url( 'admin.js', __FILE__ ),
array( 'wp-api-fetch' ),
'1.0.0',
array( 'in_footer' => true, 'strategy' => 'defer', 'fetchpriority' => 'low' )
);
} );
// WRONG - dependency-sensitive code with async.
wp_enqueue_script( 'myplugin-app', $src, array( 'jquery' ), '1.0.0', array( 'strategy' => 'async' ) );
// RIGHT
wp_enqueue_script( 'myplugin-app', $src, array( 'jquery' ), '1.0.0', array( 'strategy' => 'defer' ) );
Cross-references
- Run
wp-plugin-architecturefor broader placement of enqueue code inside plugin services. - Run
wp-i18n-auditwhen scripts need translations. - Run
wp-security-auditwhen inline boot data contains user/admin-controlled values.
What this skill does NOT cover
- Gutenberg/block editor component development.
- Build tooling such as Vite, webpack, or
@wordpress/scripts. - CDN/page-cache strategy.
References
- WordPress 6.9 frontend performance field guide: https://make.wordpress.org/core/2025/11/18/wordpress-6-9-frontend-performance-field-guide/
- Script APIs: wp-includes/functions.wp-scripts.php
- Script Modules API: wp-includes/script-modules.php
- Style APIs: wp-includes/functions.wp-styles.php