Skip to content

WordPress

Pass PHP data to JavaScript in WordPress the right way

Use wp_localize_script or wp_add_inline_script instead of echoing PHP into JS - with REST notes, shortcode loading, and a security checklist.

· Updated · 3 min read·
  • WordPress
  • JavaScript
  • wp_localize_script
  • security
Chrome developer tools inspecting page markup and styles

A client needed a shortcode-driven widget to read a ?restid= query parameter and pass it to JavaScript without loading the script on every page. The pattern still works in modern WordPress - but how you pass data has changed for the better.

This guide covers safer patterns than inline echo, when to use wp_localize_script vs wp_add_inline_script, shortcode-conditional loading, and when REST is the better tool.

Do not echo PHP into inline JavaScript

Old approach (avoid on new code):

<script>
  var restId = '<?php echo esc_js( $_GET['restid'] ?? '' ); ?>';
</script>

Problems:

  • Breaks full-page caching when the value changes per request
  • Easy to miss escaping and create XSS
  • Hard to test and to keep in sync with enqueue order
  • Tempting to grow into a pile of inline globals

Use WordPress enqueue APIs instead.

Load scripts only when needed

Pippin Williamson’s pattern - load a script only when a shortcode is present - is still valid. Hook wp_enqueue_scripts, detect the shortcode in post content (or a known block), then enqueue your file.

For block themes and the block editor, prefer block.json viewScript / script so assets load with the block rather than site-wide.

Pass data with wp_localize_script

The classic way to expose server values to a registered script:

wp_enqueue_script(
  'my-widget',
  plugins_url( 'js/widget.js', __FILE__ ),
  array(), // prefer vanilla JS unless you truly need jQuery
  '1.0.0',
  array( 'in_footer' => true )
);

$rest_id = isset( $_GET['restid'] ) ? sanitize_text_field( wp_unslash( $_GET['restid'] ) ) : '';

wp_localize_script( 'my-widget', 'MyWidgetConfig', array(
  'restId'  => $rest_id,
  'ajaxUrl' => admin_url( 'admin-ajax.php' ),
  'restUrl' => esc_url_raw( rest_url( 'myplugin/v1/' ) ),
  'nonce'   => wp_create_nonce( 'wp_rest' ),
) );

In widget.js, read MyWidgetConfig.restId. WordPress outputs a safe JSON blob before your script.

Notes:

  • The object name (MyWidgetConfig) must be a valid JS identifier
  • Values should already be sanitized PHP-side
  • Prefer rest_url() + cookie/nonce auth for modern front-end calls

Alternative: wp_add_inline_script

For block themes or builds without the classic “localize” mental model:

wp_enqueue_script( 'my-widget', $src, array(), '1.0.0', true );

$config = wp_json_encode(
  array(
    'restId' => $rest_id,
  ),
  JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP
);

wp_add_inline_script(
  'my-widget',
  'window.MyWidgetConfig = ' . $config . ';',
  'before'
);

Still escape via wp_json_encode() - do not concatenate raw user input into JS strings.

When to use the REST API instead

If the browser needs live data (search, filters, maps, carts), prefer a small REST route or carefully scoped admin-ajax.php with nonces:

  • Server validates and sanitizes on every request
  • No sensitive logic baked into the page HTML
  • Easier to test and cache independently
  • Works better with SPAs and interactive blocks

Embed bootstrap config (URLs, nonces, feature flags) with localize/inline script. Fetch business data over REST.

I still keep the original gist for reference - the idea is the same, the API names are just cleaner today.

Blocks and the Interactivity API

For newer interactive UI in WordPress, the Interactivity API can store state in directives and server-render initial state with the block. That does not replace REST for remote data, but it reduces the need for one-off global config objects on every page.

Rule of thumb:

  • Simple plugin widget → wp_localize_script
  • Block with view script → block.json assets + inline/localized config
  • Rich client UI → Interactivity API + REST as needed

Common mistakes

  • Localizing before the script is registered/enqueued (data never prints)
  • Using jQuery as a dependency “just because” on a modern script
  • Passing entire $_POST / user objects to the front end
  • Forgetting wp_unslash() on Gutenbergy / magic-quoted input paths
  • Putting secrets (API private keys) in localized data - anything in JS is public

Security checklist

  • Sanitize input (sanitize_text_field, absint, etc.)
  • Escape/encode output (wp_json_encode, never raw string concat)
  • Use nonces for mutating AJAX/REST calls
  • Capability checks on the server for privileged actions
  • Never trust the front end for authorization

Need help wiring WordPress, WooCommerce, or custom plugins? See WordPress & WooCommerce development or contact me.

Frequently asked questions

Is wp_localize_script still recommended?

Yes for simple config objects passed to a registered script. For richer bootstrapping, wp_add_inline_script with wp_json_encode() is a solid alternative. For live data, prefer REST or admin-ajax with nonces.

Why is echoing PHP into a script tag a bad idea?

It breaks caching, is easy to get XSS wrong, mixes concerns, and is harder to test. WordPress enqueue APIs exist so scripts load in order with escaped data.

When should I use the REST API instead of localize?

When the browser needs fresh or user-specific data after page load - search, filters, maps, carts - or when the payload should not be embedded in HTML.

Need help with your site?

If this is the kind of work you need done, tell me about your project or book a call.

Denmark
Roots
Barcelona
Where it grew
Miami
Current HQ
Global
Clients