Interactivity API Explained
In this lesson, I am going to show you how to create interactive blocks in WordPress, and we will get a clear understanding of what the Interactivity API is and how to use it on our projects.
I'll show you two examples: the first example will be an AJAX category filter block, which we will create step by step in the video below; the second one is much simpler, you can find it below as well.
Sorry, but you don’t have access to this video lesson.
Sign in to your account or get the course.
By the way, in the video, I will also show you an example of using the Interactivity API routers. As an option, you can download the source files of the block below:
When I first heard about the Interactivity API and saw the example website provided by WordPress (WPMovies), my first thought was that it was something about creating headless WordPress sites (so we can navigate a site asynchronously without a page refresh).
And it is not exactly that!
Of course, you can navigate the Query Block without page reload using the Interactivity API (as my taxonomy filter block does), but the key idea here is that it also allows you to create simple interactions, for example, something like opening and closing the content of an accordion block. So if you want to add some JavaScript events into your blocks, you don’t need to use jQuery or write vanilla JavaScript or whatever, you can just use the Interactivity API for that.
Let’s dive into it.
Creating Your First Interactive Block
First of all, please take a look at this AJAX category filter:
We will create this category filter step by step in the video above, plus you can download its source code below:
However, if you’re looking for a more basic example, you can take a look at the one provided by the official @wordpress/create-block tool.
And here is what it is going to look like:

As you can see from the screenshot, the example block doesn’t do anything complicated; it just shows and hides the content when you click the toggle button. A little bit later in this tutorial I will show you how to load content asynchronously into this block when you open it, but let’s start with basics.
Scaffolding an interactive block
Run the following command in your wp-content/plugins folder:
npx @wordpress/create-block --template @wordpress/create-block-interactive-templateEverything is pretty much as usual here, just follow the instructions in the command line.
If you’re already starting to lose the point, I highly recommend you take a look at my other tutorial, specifically about using the @wordress/create-block package.
Interactive block file structure
Before even going into WordPress admin, let’s take a look at the file structure that has been created by the tool:

We can see a render.php file here, which means that our block is a dynamic block (its front-end content is rendered in PHP), and you can find a lot of tutorials where interactive blocks are dynamic blocks but it is not 100% necessary, you can also use save.js file instead without problems, below is an example how.
You can also see a new file here – view.js, which is going to be loaded on the front end and basically ensures the interactive behavior of the block, the file should be specified in the block.json the following way:
"viewScriptModule": "file:./view.js"If it’s not, add this line into src/block.json file right now, because otherwise the view.js file will not be compiled for the build folder (and interactions will not work as a result).
In the block.json file you can also find this new line, just letting you know:
"supports": {
"interactivity": true
},What is in edit.js?
Nothing.
Interactive blocks are all about doing stuff on the website’s front end, so in our examples, we don’t even need to work with the edit() method of the registerBlockType() function.
Directives in render.php
Now if you take a look either at the block HTML in the browser inspector or in the render.php file, you will find a bunch of HTML data attributes there.
- The block element has
data-wp-interactive,data-wp-contextanddata-wp-watch, - The button has
data-wp-on--clickanddata-wp-bind--aria-expanded, - The expandable content area –
data-wp-bind--hidden.
Each data attribute is a directive, in a format data-[directive name], and now let’s figure out what each one of them means.
And before we dive into the description of the directives used in our example block I would like you to know one more thing.
Context — is a block local state, “local” means that it can not be accessed by other blocks. For example, our block has only one context (state) – isOpen, which can be either true when the content of the block is displayed or false – when it is hidden.
It is important to know, and now let’s continue.
| Attribute (directive) | Description |
|---|---|
data-wp-interactive | It basically just says that it is going to be an interactive block and contains your plugin namespace. Probably in the future it is even going to be added automatically by WordPress. |
data-wp-context | Provides the block default context, which can be specified as JSON or with a PHP function wp_interactivity_data_wp_context(). |
data-wp-watch | Basically, it just allows to run a callback function callbacks.FunctionName from the view.js file every time when the node is created and when the context is changed. |
data-wp-on--click | wp-on--[event] allows to run a function actions.FunctionName from the view.js file when a specific event occurs. |
data-wp-bind--aria-expanded and data-wp-bind--hidden | wp-bind--[attribute] directive allows to add you a specific HTML attribute (in our case aria-expanded and hidden) based on a boolean (attribute is added or removed) or string value. Executed every time an element is created or there is a change in the state or context. |
I think now we’re ready to take a look at our render.php file. I simplified it significantly by removing accessibility HTML attributes and translation functions.
<?php
/**
* Generates the block HTML for the front end
* @author Misha Rudrastyh
* @url https://rudrastyh.com/gutenberg/interactivity-api.html
*/
?>
<div
<?php echo get_block_wrapper_attributes() ?>
data-wp-interactive="rudr"
<?php echo wp_interactivity_data_wp_context( array( 'isOpen' => false ) ) ?>
data-wp-watch="callbacks.logIsOpen"
>
<button data-wp-on--click="actions.toggle">Toggle</button>
<p data-wp-bind--hidden="!context.isOpen">
Misha Interactive Block Example - hello from an interactive block!
</p>
</div>After reading all my explanations above, this file should be crystal clear for you guys, but if it is not, please leave a comment below and I will try to help you.
In case you’re wondering how to provide the default context with JSON, here is how:
<div
<?php echo get_block_wrapper_attributes() ?>
data-wp-interactive="rudr"
data-wp-context='{ "isOpen": false }'
data-wp-watch="callbacks.logIsOpen"
>But I will tell you once again – better use wp_interactivity_data_wp_context() for that.
view.js
Now let’s take a detailed look at a new file that appeared in interactive blocks – view.js.
import { store, getContext } from '@wordpress/interactivity'
store( 'rudr', {
actions: {
toggle: () => {
const context = getContext()
context.isOpen = ! context.isOpen
},
},
callbacks: {
logIsOpen: () => {
const { isOpen } = getContext()
// Log the value of `isOpen` each time it changes.
console.log( `Is open: ${ isOpen }` )
},
},
} )We already know something about the block context, so a part of the file should be clear for us as well, for example on the line 7 we’re changing the block context (we open the block when it is closed and vice versa). On lines 12 and 14 we just get the block context and print it into the browser console.
Now let’s take a look at things I haven’t explained to you yet:
- Actions – here we can provide callback functions for events created by
wp-on--[event]directive. - Callbacks – here we provide callback functions for
wp-watchdirective. - Store – it is kind of “a category” for actions and callbacks of your block. However, each block not necessarily should have a unique store namespace. For example, if I create a bunch of blocks, I can use the same
store( 'rudr' ...for each one of them.
I don’t want to overwhelm you with the theoretic stuff, let’s continue practicing instead.
Using wp-style–[css property] directive
What about a quick excercise?
In the example above we used the block context isOpen and its boolean value to either display or hide a <p> element with the wp-bind--hidden directive.
Let’s change the wp-bind--hidden directive to a wp-style--display one.
What is the difference?
| Directive | What it does in our case |
|---|---|
wp-bind--hidden | It displays an HTML attribute hidden which hides the <p> HTML element if the block context isOpen is set to false. |
wp-style--display | It displays style="display:[context]" HTML attribute for the <p> element, where the context value can be either block or none (because we set it that way). |
As you could’ve guessed, the wp-style--display format is wp-style--[css property], so basically, it allows you to add any inline CSS to an HTML element.
Let’s do it.
Changes for the render.php file:
<?php echo wp_interactivity_data_wp_context( array( 'display' => 'none' ) ) ?><p data-wp-style--display="context.display">Changes for the view.js file:
context.display = 'block' === context.display ? 'none' : 'block'That’s pretty much it.
save() instead of render.php
In this another let’s say exercise we’re going to make our block static, which means that we’re going to use the save() method in JavaScript instead of PHP code to generate the block content for the website.
import { useBlockProps } from '@wordpress/block-editor';
export default function Save() {
return (
<div
{...useBlockProps.save( {
'data-wp-interactive' : '{ "namespace": "rudr" }',
'data-wp-context' : '{ "isOpen": false }',
'data-wp-watch' : 'callbacks.logIsOpen',
} )}
>
<button data-wp-on--click="actions.toggle">Toggle</button>
<p data-wp-bind--hidden="!context.isOpen">
Misha Interactive Block Example - hello from an interactive block!
</p>
</div>
)
}Not very difficult, right? But I assume, if you’re working with more complex interactive blocks, it would be easier to use a render.php file instead.
Communicating Between Different Blocks
In this part I am going to show how we can interact with our initial block from another block.
Kind of like this:

Probably the first thing I should say here is about the difference between state and context.
- State – global block state, other blocks can use it as well,
- Context – local block state, unavailable for other blocks.
And it is quite obvious, that when we want to configure communication between blocks in terms of the interactivity API, we need to work with the state (not with the context).
Changes in the render.php file:
<?php
// providing the default block state
wp_interactivity_state(
'rudr',
array( 'isOpen' => false )
);
?>
<div
<?php echo get_block_wrapper_attributes() ?>
data-wp-interactive="rudr"
>
<button data-wp-on--click="actions.toggle">Toggle</button>
<p data-wp-bind--hidden="!state.isOpen">
Misha Interactive Block Example - hello from an interactive block!
</p>
</div>Now we can forget about data-wp-context directive and we need to use the wp_interactivity_state() PHP function instead.
Changes in the view.js file:
import { store } from '@wordpress/interactivity'
const { state } = store( 'rudr', {
// we can also set the default state here without wp_interactivity_state()
// state : {
// isOpen : true,
// },
actions: {
toggle: () => {
state.isOpen = !state.isOpen
}
}
} )Last but not least, here is a render.php file of our second Gutenberg block:
<?php
$block_attributes = get_block_wrapper_attributes();
?>
<div <?php echo $block_attributes ?> data-wp-interactive="rudr">
<button data-wp-on--click="actions.toggle">
Toggle from another block
</button>
</div>As you can see, we’re using the same namespace rudr and just triggering the action as usual.
Asynchronous Interactive Block Example
It is time to make our interactive block examples a little bit more interesting.

Let’s start by making some changes in our render.php file.
<?php
$block_attributes = get_block_wrapper_attributes()
?>
<div
<?php echo $block_attributes ?>
data-wp-interactive="rudr"
<?php echo wp_interactivity_data_wp_context( array( 'isOpen' => false ) ) ?>
>
<button data-wp-on--click="actions.getRandomPost">Show me a random post</button>
<p data-wp-bind--hidden="!context.isOpen">
<span data-wp-text="context.randomPost.title.rendered"></span>
</p>
</div>Basically what I am doing here is just using wp-text directive which allows printing some content into an HTML element, in our case it is a <span>.
Now it is time to figure out what is context.randomPost and where we get it from. In order to do so let’s take a look at our view.js file right now:
import { store, getContext } from '@wordpress/interactivity'
store( 'rudr', {
actions: {
*getRandomPost() { // a generator function
const context = getContext()
// close or open our accordeon
context.isOpen = ! context.isOpen
// if it is currently opened, get and display a random post
if( context.isOpen ) {
const posts = yield fetch(
'http://' + window.location.hostname + '/wp-json/wp/v2/posts'
).then( function( response ){
return response.json()
} )
context.randomPost = posts[ ( Math.floor( Math.random() * posts.length ) ) ]
}
},
}
} )Have any questions? Please let me in the comments section 🙂
Misha Rudrastyh
Hey guys and welcome to my website. For more than 10 years I've been doing my best to share with you some superb WordPress guides and tips for free.
Need some developer help? Contact me
Hi Misha,
First of all, thank you for the great article! I came across an issue while trying to follow your examples and using the `npx @wordpress/create-block-interactive-template` template command with an existing block plugin. It appears that in order to use the `viewScriptModule` field in the block.json, you need to first set the `–experimental-modules` flag in your NPM scripts, or else the block won’t work in the frontend, see: https://www.npmjs.com/package/@wordpress/scripts?activeTab=readme
Perhaps you’d like to include this as a hint.
Hi Henrik,
Thank you! 🙏
Hi Misha, Thank you for the very thorough tutorial!
Do you know if every
data-wp-on--clickdirective needs to wrapped in a “ element? Specifically, I am trying to add my own interactivity to a block that already has it’s own interactivity and getting stuck.Relatedly, do you know if blocks can have multiple
data-wp-interactiveattributes?Hi Kathy,
Didn’t have a chance to try it this way :)
Hello Misha,
Thanks for the lovely guide as always!
One question do, how would you structure this in form of 2 blocks that share state, for example wishlist items, heart is one and counter is second ?
thanks!
Hello,
You’re welcome! I haven’t tried this behavior yet :)
How would I have multiple instances of a block that can toggle the hidden property (like an accordion) and have another block like a “close all” or “expand all” button that could open/close them all?
Damn, Interactivity API is definitely a killer feature for future versions of WP!