WordPress Script Modules and Import Maps: ES Module Support in Core

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.