# 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.

Source: https://larsik.com/blog/parse-get-or-post-variables-to-javascript-via-php/

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.

May 28, 2015· Updated August 4, 2026· 3 min read·[Lars Koudal](/about/)

-   WordPress
-   JavaScript
-   wp\_localize\_script
-   security

![Chrome developer tools inspecting page markup and styles](/images/uploads/chromedeveloper-w400.webp)

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](https://pippinsplugins.com/load-scripts-if-post-has-short-code/) - 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](https://gist.github.com/lkoudal/8dd02ced21f5fdb334ee) 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](https://developer.wordpress.org/block-editor/reference-guides/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](/services/wordpress-woocommerce/) or [contact me](/contact/).

## 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.
