The WordPress Interactivity API is a built-in framework, shipped in WordPress core since version 6.5, that lets you add front-end interactivity to blocks using HTML directives instead of hand-written JavaScript or a client-side framework like React. It renders on the server first, then hydrates only the parts of the page that actually change, so a toggle, a filter, or a cart counter stays fast without a bundler.
This guide walks through seven practical steps for using the WordPress Interactivity API in a real theme or plugin: confirming support, learning the core directive vocabulary, wiring up a store with side effects, avoiding a hydration flash, comparing it against Alpine.js and React, debugging the errors you’ll actually hit, and measuring the Core Web Vitals payoff. Every example uses real directive syntax you can paste into a block today, not pseudocode.
What Is the WordPress Interactivity API?
The WordPress Interactivity API is a standard system of HTML directives — attributes such as data-wp-interactive and data-wp-on--click — connected to a small JavaScript store that holds state, actions, and side effects. Instead of every plugin shipping its own copy of jQuery or React, blocks share one runtime that WordPress core already loads. The Search, Query, Navigation, and File blocks all use it today, which is why a filterable product grid or a paginated archive feels instant without a page reload.
It did not appear out of nowhere. The Gutenberg team spent roughly two years prototyping directive-based interactivity before the feature stabilized, and the official developer note announcing the WordPress 6.5 merge describes it as the point where “blocks can finally share one small, well-tested runtime instead of each shipping their own.” That framing still matters in 2026: a theme and three unrelated plugins can each add interactive blocks to the same page without loading three different JavaScript libraries.
The official reference describes the system as “a standard, directive-based way to add frontend interactivity to blocks,” and that definition still holds today: it is not a new templating language, just a disciplined way to wire existing server-rendered HTML to live state. For the full directive and store API, see the official Interactivity API reference. WordPress also shipped a parallel system this cycle for exposing functionality to AI agents — if you haven’t looked at it yet, the WordPress Abilities API solves a related but separate problem: letting a model call WordPress functionality safely, rather than letting a visitor click something on the page.
Step 1: How Do You Check If Your Site Supports the WordPress Interactivity API?
You need WordPress 6.5 or later, because the API depends on script modules, a loading mechanism that only became available in that release. Open Tools → Site Health → Info and confirm your version number before writing any directive code; anything in the 7.x line, including the WordPress 7.1 release most sites are running in late 2026, supports it natively with no plugin required.
Next, confirm your theme or plugin registers its view script as a module, not a classic script. In block.json, that means a viewScriptModule key pointing at your JavaScript file, not viewScript. A classic script can still run on the page, but it cannot call store() from @wordpress/interactivity, so this one field is the most common reason a tutorial’s code “does nothing” when copied into a real project.
If you’re scaffolding a new block with @wordpress/create-block, pass the --variant interactive flag and the generator fills in viewScriptModule, a starter store, and a render template with directives already wired, which saves you from tracking down this one field by trial and error.
Step 2: What Directives Do You Need to Build an Interactive Block?
A small set of directives covers almost every interactive pattern. data-wp-interactive declares the namespace a block of markup belongs to. data-wp-context sets block-local state as a JSON object. data-wp-on--[event], such as data-wp-on--click, wires a DOM event to an action. data-wp-bind--[attribute] keeps an HTML attribute in sync with state, and data-wp-text keeps an element’s text content in sync. Start with a toggle, the smallest useful example:
<div data-wp-interactive="tbn-demo" data-wp-context='{ "isOpen": false }'>
<button data-wp-on--click="actions.toggle">Toggle</button>
<p data-wp-bind--hidden="!context.isOpen">Now you see me.</p>
</div>The block still renders correctly even if JavaScript never loads, because the server already printed the paragraph; the directive only controls the hidden attribute once the store takes over. That matters for both accessibility and SEO: a crawler or a screen reader that ignores JavaScript still sees real content, not an empty shell waiting to be filled in.
For lists — a set of FAQ items, filter chips, or search results — the data-wp-each directive repeats a template for every item in an array without you writing a manual loop in JavaScript:
<ul data-wp-context='{ "items": ["Apples", "Pears"] }'>
<template data-wp-each--item="context.items">
<li data-wp-text="context.item"></li>
</template>
</ul>Each repeated node gets its own isolated context, so updating one item’s state in an action never touches its siblings — the same guarantee you’d expect from a keyed list in a client-side framework, without writing any key-management code yourself.
Step 3: How Does the Store Connect Directives to State, Actions, and Side Effects?
The store is a plain JavaScript object registered once per namespace, imported from @wordpress/interactivity inside your viewScriptModule file. It holds global state shared across every instance of the block on the page, per-instance context set by data-wp-context, actions, the functions your directives call, and callbacks, side effects that re-run automatically whenever the values they read change.
import { store, getContext } from "@wordpress/interactivity";
store("tbn-demo", {
actions: {
toggle() {
const context = getContext();
context.isOpen = !context.isOpen;
},
},
callbacks: {
logOpenState() {
const { isOpen } = getContext();
console.log("panel open:", isOpen);
},
},
});Mutating context.isOpen directly is the pattern, not a shortcut: the Interactivity API wraps state in a reactive proxy, so that single assignment re-renders every directive bound to it. There is no setState call to remember and no virtual DOM diff to pay for. A callback wired to an element with data-wp-watch runs once on load and again every time a value it reads changes, which is useful for logging, analytics events, or syncing a third-party widget without polling.
None of this replaces accessibility work the directives can’t do for you. data-wp-bind--aria-expanded keeps an aria-expanded attribute in sync the same way data-wp-bind--hidden keeps visibility in sync, but you still have to add it yourself; the Interactivity API wires state to attributes, it doesn’t decide which ARIA attributes a toggle button needs. Treat every interactive block the way you’d treat a hand-written one: keyboard-operable controls, visible focus states, and attributes that describe the current state, not just the visual one.
Step 4: How Do You Avoid a Hydration Flash on the First Load?
A hydration flash happens when the server renders one visual state and the client-side store initializes to a different one a frame later, so the user sees a flicker. The fix is to set your initial state on the server, in PHP, rather than letting the client compute it from scratch, and to make sure the markup you print already matches that state.
// Inside your block's render.php, before the markup is echoed:
wp_interactivity_state( 'tbn-demo', array(
'isOpen' => false,
) );Calling wp_interactivity_state() in your render callback prints those values into a JSON script tag the client store reads on load. Because the directive-driven markup was already server-rendered with matching attributes, the browser never has to repaint anything — the JavaScript only takes over for the interactions that happen after that first paint. Skipping this step is the single most common cause of a visible flicker on an otherwise correct block.
Step 5: How Does the Interactivity API Compare to Alpine.js, React, and Plain JavaScript?
Each approach trades bundle size, hydration behavior, and learning curve differently. The table below reflects how they behave specifically inside a WordPress block context, not general framework benchmarks.
| Approach | Added JS payload | Hydration model | Best for | Learning curve |
|---|---|---|---|---|
| WordPress Interactivity API | 0 KB (built into core) | Server-first, partial | Core and plugin blocks | Moderate — new directive syntax |
| Alpine.js | ~15 KB gzipped | Client-only | Small inline widgets outside blocks | Low |
| React (manually enqueued) | 40 KB+ gzipped | Full client hydration | Complex, app-like admin UI | High |
| Plain JavaScript | 0 KB extra | None — manual DOM writes | One-off scripts, quick fixes | Low, but unmanaged at scale |
For an e-commerce filter sidebar that needs to stay indexable and fast on mobile, the Interactivity API wins because the product list is still real server-rendered HTML; a search engine and a user on a slow connection both see content immediately, and only the filtering logic needs JavaScript at all. For a small dropdown in a template part that has nothing to do with a registered block, Alpine.js remains the simpler choice — dragging in the Interactivity API’s directive model for one isolated widget is more ceremony than the problem needs.
React still has a place, but it’s the admin screen, not the front end: a drag-and-drop form builder or a multi-step settings wizard inside wp-admin is exactly the app-like interface React was designed for, and none of that code ever needs to reach a site visitor’s browser.
Step 6: How Do You Debug the Most Common Interactivity API Errors?
Four mistakes account for most “it’s not working” reports. First, a missing or mismatched data-wp-interactive namespace means directives silently fail to find your store, and the browser console shows no error at all — the directive simply never fires. Second, mutating state outside an action, from an unrelated script, breaks the reactive proxy and stops re-renders. Third, registering viewScript instead of viewScriptModule loads your file as a classic script that can’t import @wordpress/interactivity at all, which throws a module-resolution error in the console the moment the page loads.
The fourth is caching: a page cache or object cache can serve a stale wp_interactivity_state() payload after you change the initial state in PHP, so the server-rendered HTML and the client store disagree again, which looks exactly like a hydration bug even though the directive code is fine. If you’re running one of the plugins covered in our WordPress caching plugin comparison, exclude pages with interactive blocks from full-page HTML caching, or purge on every deploy that touches a block’s render callback. When in doubt, open your browser’s DevTools, check the Elements panel for the data-wp-* attributes you expect, and confirm the JSON script tag wp_interactivity_state() produces actually contains the values your PHP code set.
Step 7: How Does the Interactivity API Improve Core Web Vitals and INP?
Interaction to Next Paint, or INP, replaced First Input Delay as a Core Web Vital in March 2024 and measures how long the page takes to visually respond to a click, tap, or keypress across its entire lifespan, not just the first one; Google’s own guidance puts a “good” score at 200 milliseconds or under, per the web.dev INP documentation. Because Interactivity API directives update only the DOM nodes bound to the state that changed, instead of re-rendering a whole component tree, interactions tend to resolve well inside that budget.
You can see the real-world number in two places: the Core Web Vitals report in Google Search Console, which aggregates field data from actual visitors over the previous 28 days, or a single-page lab test in PageSpeed Insights for a quick before-and-after comparison right after you ship a change.
If INP is already a known problem on your site, the fix usually isn’t the Interactivity API itself — it’s everything else blocking the main thread while your directive tries to run. Our guide to fixing WordPress INP covers the render-blocking scripts and oversized event handlers that usually need clearing out first, since no amount of efficient directive code helps if the main thread is already busy with something unrelated when the click happens.
Keep lab and field data separate when you judge the result. A PageSpeed Insights run right after deploying a change tells you whether the code itself got faster, but your real INP score is whichever number Search Console reports from actual visitors over the following weeks; a quiet lab score and a stubborn field score usually means the bottleneck is a visitor’s device or network, not the directive code you just shipped.

Key Takeaways Before You Start Building
WordPress still runs roughly 43% of all websites as of January 2026, according to W3Techs, which is exactly why core shipped a shared interactivity runtime instead of leaving every theme and plugin to bundle its own: at that scale, duplicated JavaScript libraries add up to a real, measurable performance cost across the web. The WordPress Interactivity API is core’s answer to that problem, not a replacement for every front-end framework everywhere.
Before you start building, confirm three things: your WordPress version is 6.5 or later, your view script is registered as a module rather than a classic script, and your initial state is set server-side with wp_interactivity_state() so the first paint never flickers. Get those three right and the directive syntax itself — data-wp-context, data-wp-on, data-wp-bind, and data-wp-each — covers the overwhelming majority of blocks you’ll ever need to build.
Treat it as the default for any block meant to ship on a public-facing page, reach for Alpine.js only for small widgets that live entirely outside the block system, and keep React where it already belongs: behind the login screen, inside wp-admin, where bundle size never touches a visitor’s Core Web Vitals score.
Frequently Asked Questions
Does the WordPress Interactivity API replace React in the block editor?
No. The block editor itself is still built in React. The Interactivity API only governs what runs on the published, front-end page a visitor sees — the editor’s React code is never sent to them.
Do I need to know JavaScript to use the WordPress Interactivity API?
You need enough JavaScript to write a store with actions, but not a framework. If you can write a plain function and import a module, you can register a store; the directives themselves are written as HTML attributes, not JavaScript.
Which WordPress version added the Interactivity API?
WordPress 6.5, released in April 2024, merged the Interactivity API into core along with script modules, the loading mechanism it depends on. Every release since, including the 7.x line, keeps it available by default with no plugin required.
Does the Interactivity API work with classic, non-block themes?
It works anywhere WordPress can enqueue a script module and render a block, which includes classic themes that embed blocks via the_content or a shortcode-wrapped block. It does not require a full block theme or Full Site Editing.
Will the WordPress Interactivity API slow down my site?
The runtime itself is small and, once a theme or plugin needs it, shared across every block that uses it, rather than duplicated per plugin. Used correctly — server state set up front, actions instead of stray scripts — it tends to help INP rather than hurt it.
Can I use the Interactivity API inside Elementor or other page builders?
Only if the content is rendered as an actual Gutenberg block; Elementor, Divi, and similar builders store layouts in their own formats and don’t load script modules the same way. For a block-only section embedded inside a builder page, it can still work, but the builder’s own widgets won’t gain directive support.
Subscribe for Newsletter

