WordPress 6.5 introduced the wp_register_script_module() / wp_enqueue_script_module() API alongside automatic import map generation. This means you can now ship native ES modules to the browser — with bare specifier imports like import { store } from '@wordpress/interactivity' — without a bundler step in the critical path.
Problem: WordPress enqueues JavaScript as classic scripts with wp_enqueue_script() — using native ES module import/export syntax in plugin code requires a bundler, and multiple plugins may load conflicting versions of the same module.
Solution: Use wp_enqueue_script_module() (WordPress 6.5+) to register native ES modules. WordPress generates an import map that maps module specifiers like @wordpress/interactivity to their URLs, adds type="module" to the script tag, and deduplicates shared modules across plugins automatically.
The snippet below registers two modules (a utility module and a main module that imports it), generates the import map automatically, and shows the resulting HTML that WordPress outputs.
<?php
// Register a utility ES module
add_action( 'wp_enqueue_scripts', function () {
// 1. Register a dependency module
wp_register_script_module(
'my-utils', // module ID (used as import specifier)
get_theme_file_uri( 'js/utils.mjs' ),
[], // no deps
'1.0.0'
);
// 2. Register the main module that imports 'my-utils' and wp/interactivity
wp_enqueue_script_module(
'my-app',
get_theme_file_uri( 'js/app.mjs' ),
[
[ 'id' => 'my-utils', 'import' => 'dynamic' ],
[ 'id' => '@wordpress/interactivity','import' => 'static' ],
],
'1.0.0'
);
} );
// js/utils.mjs
export function formatDate( isoString ) {
return new Intl.DateTimeFormat( document.documentElement.lang || 'en',
{ dateStyle: 'medium' } ).format( new Date( isoString ) );
}
// js/app.mjs
import { store, getContext } from '@wordpress/interactivity';
// dynamic import resolved via the WordPress-generated import map:
const { formatDate } = await import( 'my-utils' );
store( 'my-store', {
actions: {
showDate() {
const ctx = getContext();
ctx.formattedDate = formatDate( ctx.isoDate );
},
},
} );
NOTE: Script modules are output with type="module" and are always deferred; you cannot mix wp_enqueue_script_module() with wp_add_inline_script() — use a top-level await or a custom event to pass data from PHP to your module instead.