Script Modules API
Native support for ES Modules and Import Maps in WordPress.
Since: 6.5.0
Source: wp-includes/script-modules.php, wp-includes/class-wp-script-modules.php
Components
| Component | Description |
|---|---|
| functions.md | Core registration and enqueue functions |
| class-wp-script-modules.md | Script modules registry class |
| hooks.md | Actions and filters |
What Are Script Modules?
Script Modules are WordPress’s implementation of native ES Modules (ESM). Unlike traditional scripts loaded with wp_enqueue_script(), script modules:
- Use
<script type="module">tags - Support native
import/exportsyntax - Leverage browser import maps for dependency resolution
- Are deferred by default (no blocking)
- Have module scope (not global)
Import Maps
Import maps allow bare module specifiers (like @wordpress/interactivity) to resolve to actual URLs. WordPress automatically generates an import map containing all registered script modules and their dependencies.
<script type="importmap" id="wp-importmap">
{
"imports": {
"@wordpress/interactivity": "/wp-includes/js/dist/script-modules/interactivity/index.min.js?ver=6.7"
}
}
</script>
Registration Flow
wp_register_script_module( $id, $src, $deps, $version, $args )
└── WP_Script_Modules::register()
└── Store in $registered array
Enqueue Flow
wp_enqueue_script_module( $id )
└── WP_Script_Modules::enqueue()
└── Add to $queue array
Output Flow
add_hooks()
├── wp_head (block themes) or wp_footer (classic themes)
│ ├── print_import_map()
│ └── print_script_module_preloads()
│
├── wp_head (block themes only)
│ └── print_head_enqueued_script_modules()
│
└── wp_footer
├── print_enqueued_script_modules()
├── print_script_module_data()
└── print_a11y_script_module_html()
Block Themes vs Classic Themes
| Behavior | Block Themes | Classic Themes |
|---|---|---|
| Import map location | wp_head |
wp_footer |
| Head script modules | Supported (in_footer: false) |
Not supported |
| Module preloads | wp_head |
wp_footer |
In classic themes, script modules used by blocks aren’t known at wp_head, so everything prints in the footer.
Dependencies
Dependencies can be static or dynamic:
// Static dependency (preloaded, in import map)
wp_register_script_module( 'my-module', $src, array( 'other-module' ) );
// Dynamic dependency (in import map only, not preloaded)
wp_register_script_module( 'my-module', $src, array(
array( 'id' => 'lazy-module', 'import' => 'dynamic' )
) );
- Static: Loaded immediately via
<link rel="modulepreload">and included in import map - Dynamic: Only included in import map for runtime
import()calls
Fetch Priority
Script modules support the fetchpriority attribute (since 6.9.0):
wp_register_script_module( 'critical-module', $src, array(), false, array(
'fetchpriority' => 'high'
) );
| Value | Behavior |
|---|---|
'auto' |
Browser default (default) |
'low' |
Lower priority, non-critical path |
'high' |
Higher priority, critical path |
WordPress core modules (@wordpress/interactivity, @wordpress/block-library/*, @wordpress/a11y) use fetchpriority: low and in_footer: true since they’re not needed for critical rendering.
Default Script Modules
WordPress registers these modules via wp_default_script_modules():
| Module ID | Description |
|---|---|
@wordpress/interactivity |
Interactivity API runtime |
@wordpress/interactivity-router |
Client-side navigation |
@wordpress/block-library/* |
Block-specific view scripts |
@wordpress/a11y |
Accessibility announcements |
Example Usage
Register and Enqueue
// Register a script module
wp_register_script_module(
'my-plugin/gallery',
plugin_dir_url( __FILE__ ) . 'js/gallery.js',
array( '@wordpress/interactivity' ),
'1.0.0'
);
// Enqueue it
wp_enqueue_script_module( 'my-plugin/gallery' );
Register and Enqueue in One Call
wp_enqueue_script_module(
'my-plugin/gallery',
plugin_dir_url( __FILE__ ) . 'js/gallery.js',
array( '@wordpress/interactivity' ),
'1.0.0'
);
Passing Data to Modules
add_filter( 'script_module_data_my-plugin/gallery', function( $data ) {
$data['apiEndpoint'] = rest_url( 'my-plugin/v1/images' );
$data['nonce'] = wp_create_nonce( 'wp_rest' );
return $data;
} );
In JavaScript:
const dataEl = document.getElementById( 'wp-script-module-data-my-plugin/gallery' );
const data = dataEl ? JSON.parse( dataEl.textContent ) : {};
console.log( data.apiEndpoint );
Best Practices
- Use namespaced IDs:
my-plugin/module-nameprevents collisions - Prefer static dependencies: Only use dynamic for lazy-loaded code
- Set appropriate fetchpriority: Use
lowfor non-critical modules - Use
in_footerfor non-critical modules: Especially in block themes - Don’t mix with
wp_enqueue_script(): Keep ES modules separate from classic scripts