The WordPress Interactivity API: directives, store and when to use it
The WordPress Interactivity API: directives, store and when to use it

The Interactivity API is WordPress's built-in standard for adding front-end behavior to blocks: toggles, tabs, filters, "add to cart" buttons, instant search and client-side page navigation. Introduced in WordPress 6.5, it works by adding data-wp-* attributes (directives) to server-rendered HTML and connecting them to a small JavaScript store. WordPress processes the directives in PHP before the page is sent, so visitors see correct markup immediately, and a lightweight runtime makes it reactive in the browser.
The official reference describes it as "a standard way for developers to add interactions to the front end of their blocks," and notes that core blocks including Search, Query, Navigation and File already use it. It is supported by the @wordpress/interactivity package, bundled in core since 6.5.
As of October 2026 (WordPress 7.1.2), the API is mature enough for production plugins and themes. This guide explains the moving parts, walks through a complete toggle example, compares the API with React and plain JavaScript, and covers the 7.0 additions developers should know about.
Key Takeaways
- The Interactivity API adds behavior to server-rendered HTML through data-wp-* directives linked to a JavaScript store, without re-rendering the block in the browser.
- A block opts in with "supports": { "interactivity": true } and loads its code as a script module through viewScriptModule in block.json.
- Global state lives in the store and can be initialized in PHP with wp_interactivity_state(); local state lives in data-wp-context on an element.
- The official FAQ puts the shared runtime at about 10 KB, loaded once for every interactive block on the page.
- Directives accept references to store properties, not JavaScript expressions, so they do not use eval() and work with strict content security policies.
- WordPress 7.0 added a watch() function for store-level side effects and made core/router's state.url available on the server.
How the Interactivity API works
According to the official FAQ, the API has three main components:
- Preact with Preact Signals for hydration, client logic and client-side navigation.
- HTML directives that both the server and the client understand.
- Server-side processing handled by WordPress's HTML Tag Processor.
When a page is requested, WordPress reads the directives in your block's output, evaluates them against the initial state and context, and writes the result into the HTML (for example, adding the hidden attribute to a closed panel). In the browser, the runtime attaches to the same markup and updates only what changes when state changes. Because the server already produced the correct initial HTML, there is no flash of empty placeholders and no need to duplicate templates in PHP and JavaScript.
The FAQ also confirms that WordPress has no plans to move the block editor itself from React to Preact. The Interactivity API is for the front end; you still write the editor side of a block in React.
Requirements and setup
You need WordPress 6.5 or later and the usual block development tooling (Node.js, a code editor and a local site). The fastest start is the official template: npx @wordpress/create-block@latest my-first-interactive-block --template @wordpress/create-block-interactive-template. If you are adding the API to an existing block, there are four steps from the Interactivity API reference:
- Install the package: npm install @wordpress/interactivity --save.
- Add "interactivity": true to supports in block.json.
- Load your code as a script module with "viewScriptModule": "file:./view.js". The reference notes that wp-scripts needs the --experimental-modules flag on its build and start scripts for this; the interactive template already includes it.
- Add data-wp-interactive="your-namespace" to the block's wrapper element in render.php or save.js. Directives only work inside an element that has this attribute (or inside its children).
New to block development in general? Start with our guide to building custom WordPress blocks, then come back here.
The directives you will use most
Directives are HTML attributes with the data-wp- prefix. Their values are references to store properties such as state.isOpen, context.count or actions.toggle, optionally negated with !. The full list is in the Directives and Store reference.
| Directive | What it does | Example |
|---|---|---|
| data-wp-interactive | Activates the API for an element and its children and sets the store namespace | data-wp-interactive="my-plugin" |
| data-wp-context | Defines local state for an element and its descendants | data-wp-context='{ "isOpen": false }' |
| data-wp-bind | Sets an HTML attribute from state | data-wp-bind--hidden="!context.isOpen" |
| data-wp-class | Adds or removes a class | data-wp-class--is-active="context.isOpen" |
| data-wp-style | Sets an inline style property | data-wp-style--color="state.color" |
| data-wp-text | Sets the element's text content | data-wp-text="state.count" |
| data-wp-on | Runs an action on a DOM event | data-wp-on--click="actions.toggle" |
| data-wp-on-window, data-wp-on-document | Listens to events on window or document | data-wp-on-window--resize="callbacks.measure" |
| data-wp-watch | Runs a callback when the state it reads changes | data-wp-watch="callbacks.logIsOpen" |
| data-wp-init | Runs a callback once when the element is created | data-wp-init="callbacks.setup" |
| data-wp-run | Runs a callback during rendering, so it can use hooks | data-wp-run="callbacks.useSomething" |
| data-wp-each, data-wp-key | Renders a list from an array and keys each item | data-wp-each="state.items" on a template element |
The store: state, context, actions and callbacks
The store holds the logic and data that directives reference. It is usually created in view.js with store( 'namespace', { ... } ) and can contain:
- State: global data available to every element in the namespace on the page.
- Context: local data defined with data-wp-context and read inside actions with getContext(). Each block instance gets its own copy, which is why context suits per-instance UI such as accordions.
- Derived state: getter functions that compute values from other state, for example a total from a list of items.
- Actions: functions usually triggered by data-wp-on. Asynchronous work uses generator functions (function* with yield) so the runtime can restore the correct scope after each await-style step.
- Callbacks: functions used by data-wp-watch, data-wp-init and similar directives for side effects.
State can also start on the server. In render.php, wp_interactivity_state( 'my-plugin', array( 'ajaxUrl' => admin_url( 'admin-ajax.php' ), 'nonce' => wp_create_nonce( 'my_plugin_action' ) ) ) makes those values available to both server-side directive processing and the client store. The reference points out that this lets you use WordPress APIs such as translations (__() and _e()) and nonces when building initial state. Helper functions getElement(), getServerState() and getServerContext() cover more advanced cases, and wp_interactivity_config() passes static configuration that is not meant to change.
One rule worth memorizing: since WordPress 6.8, an action that needs synchronous access to the event object (event.preventDefault(), event.stopPropagation(), event.stopImmediatePropagation() or event.currentTarget) must be wrapped in withSyncEvent(). Otherwise you get a deprecation warning, because the runtime is moving to handle actions asynchronously by default.
A complete example: an accessible toggle
This example shows a button that shows and hides a panel. It assumes a dynamic block with "supports": { "interactivity": true } and "viewScriptModule": "file:./view.js" in block.json.
render.php
- Open the wrapper: <div <?php echo get_block_wrapper_attributes(); ?> data-wp-interactive="my-plugin" <?php echo wp_interactivity_data_wp_context( array( 'isOpen' => false ) ); ?>>
- Add the button: <button type="button" data-wp-on--click="actions.toggle" data-wp-bind--aria-expanded="context.isOpen" aria-controls="my-panel"><?php echo esc_html__( 'Show details', 'my-plugin' ); ?></button>
- Add the panel: <p id="my-panel" data-wp-bind--hidden="!context.isOpen"><?php echo esc_html( $attributes['details'] ); ?></p>
- Close the wrapper with </div>.
wp_interactivity_data_wp_context() builds a correctly escaped data-wp-context attribute from a PHP array. In a real block, generate a unique panel ID per instance (for example with wp_unique_id( 'my-panel-' ), escaped with esc_attr()) so two copies of the block on one page do not share an ID.
view.js
import { store, getContext } from '@wordpress/interactivity'; store( 'my-plugin', { actions: { toggle() { const context = getContext(); context.isOpen = ! context.isOpen; }, }, } );
That is the whole client-side program. On the first request, WordPress processes data-wp-bind--hidden on the server, so the panel arrives hidden and aria-expanded is already "false". In the browser, clicking the button flips context.isOpen and the runtime updates both attributes. Each instance of the block has its own context, so multiple toggles on a page work independently.
When to use it (and when not to)
| Approach | Good fit | Trade-offs |
|---|---|---|
| Interactivity API | Interactive blocks, cross-block communication (a cart block reacting to an "add to cart" block), client-side navigation, anything server-rendered by WordPress | Learn directives and the store; logic must fit a reactive, declarative model |
| React on the front end | Self-contained app-like widgets (complex dashboards, editors) that do not depend on WordPress filters altering markup | The FAQ notes React rendering does not work smoothly with PHP server rendering; content often loads client-side, and server filters are lost after hydration |
| Plain JavaScript or jQuery (viewScript) | Very small, isolated enhancements | Imperative code grows hard to maintain; no shared standard for blocks to talk to each other |
The FAQ's argument for the API over React is practical. If a plugin filter adds a class to your server-rendered block, React would drop it on hydration; directives enhance the existing HTML, so filters and translations keep working. Compared with jQuery or vanilla JavaScript, the API is declarative and reactive, and blocks built on it can be combined without conflicts. Existing interactive blocks do not have to be migrated, and blocks using different approaches can coexist on the same page.
The API is not limited to blocks. wp_interactivity_process_directives() can process directives in arbitrary HTML, so classic themes and shortcode output can use it too.
Performance, security and accessibility
- Size. The FAQ states the runtime "is just ~10 KB" and is loaded once for all blocks that use it.
- Loading. Interactivity script modules, including your view.js files, load without blocking page rendering.
- No eval. Directive values are references, not code, so the FAQ says injecting JavaScript through directives is not possible, and the API does not need unsafe-eval in a content security policy.
- Escaping still applies. The server-side content you print in render.php still needs normal escaping; see our WordPress coding standards guide.
- Accessibility. Because state drives attributes, it is easy to keep aria-expanded, aria-controls and hidden in sync. Use real button elements for actions.
If your block fetches data, actions can call the REST API like any JavaScript function. Pass a REST nonce through server state and protect the endpoint with a permission_callback, as described in our WordPress REST API guide.
What changed in WordPress 7.0
The 7.0 dev note lists three changes:
- watch(): a new function in @wordpress/interactivity that runs a callback and re-runs it whenever reactive values it reads change. Unlike data-wp-watch, it is not tied to a DOM element, so it suits store-level logging, analytics or syncing state between stores. It returns an unwatch function, and the callback can return a cleanup function.
- state.url on the server: core/router's state.url is now populated during server-side directive processing, so it is defined from the start and only changes on the first client-side navigation. Combined with watch(), that gives a reliable way to send a page view on every client-side navigation.
- Deprecated router internals: reading state.navigation from the core/router store now triggers a console warning in development mode; the dev note says 7.1 would add an official mechanism for tracking navigation state.
For a wider view of the release, see what's new in WordPress 7.0 and 7.1.
Common mistakes
- Forgetting data-wp-interactive. Directives outside an interactive region are ignored.
- Loading view.js as a classic script. Use viewScriptModule, not viewScript, so it can import @wordpress/interactivity.
- Putting per-instance data in global state. Use context for anything that differs between copies of a block.
- Calling event.preventDefault() without withSyncEvent() since 6.8.
- Mismatched initial state. Initialize values in PHP with wp_interactivity_state() or context so the server-rendered HTML matches what the client expects.
- Testing only in the editor. Directives run on the front end; check the published page, ideally with caching enabled, because nonces in cached HTML can expire.
Those cache and nonce interactions are a frequent cause of "works logged in, fails for visitors" bugs. Our post on common WordPress development errors covers more debugging patterns, and the PageSpeed developer playbook explains how to keep front-end JavaScript lean.
Conclusion
The Interactivity API gives WordPress a shared, server-friendly way to make blocks interactive. You render HTML in PHP as usual, add a handful of directives, and describe behavior in a small store. It keeps first paint fast, respects WordPress filters and translations, and lets blocks from different plugins work together. Use it as the default for interactive blocks, reserve React for self-contained app-style widgets, and remember the 6.8 withSyncEvent() rule and the 7.0 watch() helper. More developer guides are in our WordPress hub, and eSEOspace's plugin development team builds interactive blocks for client sites.
Frequently asked questions
Which WordPress version do I need for the Interactivity API?
WordPress 6.5 or later, where @wordpress/interactivity is bundled in core. On older versions the reference says you need the Gutenberg plugin 17.5 or later, but every supported site should be on the current 7.1 branch anyway.
Do I still need React if I use the Interactivity API?
Yes, for the editor side of a block. The Interactivity API handles front-end behavior; the block's edit interface is still a React component.
Can I use the Interactivity API in a classic theme?
Yes. It is not limited to blocks, and wp_interactivity_process_directives() can process directives in arbitrary HTML, such as shortcode or template output.
Is the Interactivity API safe from XSS?
Directives only accept references to store properties, so the API never evaluates JavaScript strings. You still have to escape any data you print into the HTML on the server.
How do blocks share data with each other?
Blocks that use the same store namespace share global state, and one block can read or update another namespace's public store. The reference's example is an "add to cart" block updating a separate cart block.
Put this into action with eSEOspace
We help businesses grow with website design that actually performs. Explore the services behind this guide:
Get a FREE Audit
We'll perform a comprehensive SEO, AEO, GEO & CRO audit of your website — completely free — and show you exactly how to outrank your competitors.
Don't have a site yet? Get in touch →
Get a FREE GEO/AEO/SEO Audit
We'll analyze your site's SEO, GEO, AEO & CRO — completely free — and show you exactly how to get found across Google and AI answers.
Don't have a site yet? Get in touch →
Great — your audit is on the way!
We'll send your free SEO/GEO/AEO/CRO audit within the next few hours. Where should we send it?
You're all set! ✓
Your free audit is being prepared — check your inbox in the next few hours. Talk soon!
On this page
- Key Takeaways
- How the Interactivity API works
- Requirements and setup
- The directives you will use most
- The store: state, context, actions and callbacks
- A complete example: an accessible toggle
- When to use it (and when not to)
- Performance, security and accessibility
- What changed in WordPress 7.0
- Common mistakes
- Conclusion
- Frequently asked questions





