You call wp_add_inline_script() inside a shortcode. It works fine on classic themes. You switch to a block theme — or your user does — and suddenly: nothing. No PHP error, no warning, just a silent failure and a X is not defined in the browser console.
This is a known inconsistency tracked in WordPress Trac #54958. Here’s what’s actually happening and how to fix it properly.
The Problem
The difference comes down to when shortcodes run relative to wp_head().
In a classic theme:
wp_enqueue_scriptsfires insidewp_head()→ your handle gets registered- Content renders → shortcode executes →
wp_add_inline_script('my-handle', ...)✅ handle already exists
In a block theme (FSE):
get_the_block_template_html()renders blocks and shortcodes beforewp_head()- Shortcode executes →
wp_add_inline_script('my-handle', ...)❌ handle doesn’t exist yet wp_head()fires →wp_enqueue_scripts→wp_register_script(...)— too late
This is intentional behavior in WordPress core. The comment in template-canvas.php says it explicitly:
“This needs to run before
<head>so that blocks can add scripts and styles inwp_head().”
Why wp_add_inline_script specifically breaks
This is the subtle part. Not everything breaks — just wp_add_inline_script.
wp_enqueue_script('handle') without prior registration still works. WordPress adds the handle to a queue and has a mechanism to automatically re-enqueue a prematurely enqueued script once it gets registered — it resolves everything later when printing scripts.
wp_add_inline_script('handle', ...) is different. It immediately accesses $wp_scripts->registered['handle'] at call time. If the handle isn’t registered yet, it returns false — silently. No error, no warning.
So: enqueue tolerates a not-yet-registered handle. Inline script doesn’t. That’s the whole trap.
The Fix: register on init
Move wp_register_script to the init hook. It runs before blocks render, so the handle is ready when the shortcode needs it.
// ❌ Before — registration on wp_enqueue_scripts (too late for FSE)
add_action( 'wp_enqueue_scripts', function() {
wp_register_script( 'my-handle', plugin_dir_url(__FILE__) . 'script.js', [], '1.0', true );
wp_localize_script( 'my-handle', 'myData', [ 'ajax_url' => admin_url('admin-ajax.php') ] );
});
// ✅ After — registration on init (before block rendering)
add_action( 'init', function() {
wp_register_script( 'my-handle', plugin_dir_url(__FILE__) . 'script.js', [], '1.0', true );
});
add_action( 'wp_enqueue_scripts', function() {
// wp_localize_script works here — handle is already registered
wp_localize_script( 'my-handle', 'myData', [ 'ajax_url' => admin_url('admin-ajax.php') ] );
});
// Shortcode — no changes needed
function my_shortcode() {
wp_add_inline_script( 'my-handle', 'const config = ' . wp_json_encode($data) . ';', 'before' );
wp_enqueue_script( 'my-handle' );
return '<div>...</div>';
}
The shortcode stays untouched. Just move the registration earlier. This works for both classic and block themes — registering on init is explicitly documented as valid in the WordPress developer reference.
What doesn’t work as an alternative
enqueue_block_assets: fires inside wp_enqueue_scripts — same timing problem.
wp_is_block_theme() conditional: fragile, duplicates code, breaks if the user switches themes. It forces you to maintain two code paths for something that has a single clean solution.
Hook execution order (reference)
plugins_loaded
└─ init ← register scripts here
└─ wp_loaded
└─ template_redirect
└─ [FSE] get_the_block_template_html() ← shortcodes run here
└─ wp_head()
└─ wp_enqueue_scripts ← localize/enqueue here
└─ [Classic] the_content / shortcodes ← classic themes run here
└─ wp_footer
└─ wp_print_footer_scripts
Practical Rule
If your plugin uses wp_add_inline_script() inside a shortcode: register the script on init, localize on wp_enqueue_scripts, enqueue inside the shortcode. Zero conditional logic, works everywhere.
I ran into this while working on a plugin that uses shortcodes to render dynamic content. Took longer than I’d like to admit to figure out that the fix was a one-line change in hook priority — not anything inside the shortcode itself.
References:
- WordPress Trac #54958 — “Inconsistent behaviour for wp_add_inline_script between block-based and standard themes”
- wp_add_inline_script() — Developer Reference
- wp_register_script() — Developer Reference
- template-canvas.php — WordPress Core (GitHub)
- Block Editor Handbook — Enqueueing Assets
- Theme Handbook — Including Assets