All skills
wordpress avatar

/wp-interactivity-api

@20324d2 official
by wordpresswordpress/agent-skills2.2k stars
327

Use when building or debugging WordPress Interactivity API features (data-wp-* directives, @wordpress/interactivity store/state/actions, block viewScriptModule integration, wp_interactivity_*()) including performance, hydration, and directive behavior.

Use this Skill: https://skilld.dev/gh/wordpress/agent-skills/wp-interactivity-api

This session only. Nothing lands on disk.

referencesserver-side-rendering.md

≈1.9k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Server-Side Rendering for Interactivity API

  • Faster initial render: HTML arrives ready with correct values.
  • No layout shift: Hidden elements stay hidden from the first paint.
  • SEO benefits: Search engines see fully rendered content.
  • Graceful degradation: Content displays correctly even before JavaScript loads.

Setup Requirements

1. Enable Server Directive Processing

For components using block.json:

{
  "supports": {
    "interactivity": true
  }
}

For themes/plugins without block.json:

Use wp_interactivity_process_directives() to manually process directives (see "Themes and Plugins without block.json" section below).

2. Initialize Global State with wp_interactivity_state()

Define initial state values in PHP before rendering:

wp_interactivity_state( 'myPlugin', array(
  'fruits'    => array( 'Apple', 'Banana', 'Cherry' ),
  'isLoading' => false,
  'count'     => 3,
));

The state is serialized and available to client JavaScript automatically.

3. Initialize Local Context with wp_interactivity_data_wp_context()

For element-scoped context:

<?php
$context = array(
  'isOpen'   => false,
  'itemId'   => 42,
  'itemName' => 'Example',
);
?>
<div
  data-wp-interactive="myPlugin"
  <?php echo wp_interactivity_data_wp_context( $context ); ?>
>
  <button data-wp-on-async--click="actions.toggle">
    Toggle
  </button>
  <div data-wp-bind--hidden="!context.isOpen">
    Content for <?php echo esc_html( $context['itemName'] ); ?>
  </div>
</div>

Derived State on the Server

When derived state affects the initial HTML, define it in PHP to avoid layout shifts.

Static Derived State

When the derived value is known at render time:

$fruits    = array( 'Apple', 'Banana', 'Cherry' );
$hasFruits = count( $fruits ) > 0;

wp_interactivity_state( 'myPlugin', array(
  'fruits'    => $fruits,
  'hasFruits' => $hasFruits,
));

Dynamic Derived State (using closures)

When the value depends on context (e.g., inside data-wp-each loops):

wp_interactivity_state( 'myPlugin', array(
  'fruits'       => array( 'apple', 'banana', 'cherry' ),
  'shoppingList' => array( 'apple', 'cherry' ),
  'onShoppingList' => function() {
    $state   = wp_interactivity_state();
    $context = wp_interactivity_get_context();
    return in_array( $context['item'], $state['shoppingList'] ) ? 'Yes' : 'No';
  },
));

The closure is evaluated during directive processing for each element.

Complete Example: List with Server Rendering

PHP (render callback or template)

<?php
$fruits = array( 'Apple', 'Banana', 'Cherry' );

wp_interactivity_state( 'myFruitPlugin', array(
  'fruits'    => $fruits,
  'hasFruits' => count( $fruits ) > 0,
  'mango'     => __( 'Mango' ),
));
?>

<div data-wp-interactive="myFruitPlugin">
  <button data-wp-on-async--click="actions.addMango">
    <?php esc_html_e( 'Add Mango' ); ?>
  </button>
  <button data-wp-on-async--click="actions.clearAll">
    <?php esc_html_e( 'Clear All' ); ?>
  </button>

  <ul data-wp-bind--hidden="!state.hasFruits">
    <template data-wp-each="state.fruits">
      <li data-wp-text="context.item"></li>
    </template>
  </ul>

  <p data-wp-bind--hidden="state.hasFruits">
    <?php esc_html_e( 'No fruits available.' ); ?>
  </p>
</div>

JavaScript (view.js)

import { store, getContext } from '@wordpress/interactivity';

const { state } = store( 'myFruitPlugin', {
  state: {
    get hasFruits() {
      return state.fruits.length > 0;
    },
  },
  actions: {
    addMango() {
      state.fruits.push( state.mango );
    },
    clearAll() {
      state.fruits = [];
    },
  },
});

Rendered Output (initial HTML)

<div data-wp-interactive="myFruitPlugin">
  <button data-wp-on-async--click="actions.addMango">Add Mango</button>
  <button data-wp-on-async--click="actions.clearAll">Clear All</button>

  <ul>
    <li>Apple</li>
    <li>Banana</li>
    <li>Cherry</li>
  </ul>

  <p hidden>No fruits available.</p>
</div>

The hidden attribute is added server-side because state.hasFruits is true.

Serializing Values for Client Use

Use wp_interactivity_state() to pass server values to client JavaScript:

Translations

wp_interactivity_state( 'myPlugin', array(
  'labels' => array(
    'add'    => __( 'Add Item', 'textdomain' ),
    'remove' => __( 'Remove Item', 'textdomain' ),
    'empty'  => __( 'No items found', 'textdomain' ),
  ),
));

Ajax URLs and Nonces

wp_interactivity_state( 'myPlugin', array(
  'ajaxUrl' => admin_url( 'admin-ajax.php' ),
  'nonce'   => wp_create_nonce( 'myPlugin_nonce' ),
  'restUrl' => rest_url( 'myPlugin/v1/' ),
));

Client Usage

const { state } = store( 'myPlugin', {
  actions: {
    *fetchData() {
      const formData = new FormData();
      formData.append( 'action', 'my_action' );
      formData.append( '_ajax_nonce', state.nonce );

      const response = yield fetch( state.ajaxUrl, {
        method: 'POST',
        body: formData,
      });
      return yield response.json();
    },
  },
});

Themes and Plugins without block.json

For themes or plugins not using block.json, use wp_interactivity_process_directives():

<?php
wp_interactivity_state( 'myTheme', array(
  'menuOpen' => false,
));

ob_start();
?>

<nav
  data-wp-interactive="myTheme"
  data-wp-class--is-open="state.menuOpen"
>
  <button data-wp-on-async--click="actions.toggleMenu">
    Menu
  </button>
  <ul data-wp-bind--hidden="!state.menuOpen">
    <li><a href="/">Home</a></li>
    <li><a href="/about">About</a></li>
  </ul>
</nav>

<?php
$html = ob_get_clean();
echo wp_interactivity_process_directives( $html );

Only call wp_interactivity_process_directives() once at the outermost template level.

PHP Helper Functions Reference

Function Purpose
wp_interactivity_state( $namespace, $state ) Initialize/get global state for a namespace
wp_interactivity_data_wp_context( $context ) Generate data-wp-context attribute
wp_interactivity_get_context( $namespace ) Get current context during directive processing
wp_interactivity_process_directives( $html ) Manually process directives (themes/plugins)

Common Pitfalls

Server Directive Processing Not Enabled

For block.json users: Without supports.interactivity, directives are not processed:

{
  "supports": {
    "interactivity": true
  }
}

For themes/plugins: Ensure wp_interactivity_process_directives() is called on the HTML output.

Derived State Missing on Server

If state.hasFruits is only defined in JavaScript, the hidden attribute won't be set:

<!-- Without server state: shows briefly then hides (layout shift) -->
<p data-wp-bind--hidden="state.hasFruits">No fruits</p>

State Not Matching Client Expectations

Ensure PHP and JavaScript derived state logic matches:

// PHP
'hasFruits' => count( $fruits ) > 0,
// JavaScript - must match PHP logic
get hasFruits() {
  return state.fruits.length > 0;
}

External References

Source: SKILL.md on GitHub

1 warning16d5 checks · Risk SAFE
  • Gen Agent Trust Hub16d

    No security issues were identified. The skill provides standard documentation and guidelines for working with the WordPress Interactivity API.

  • Socket16d

    No alerts

  • Snyk16d

    Risk: LOW · No issues

  • Runlayer7mo

    4/4 files flagged

  • ZeroLeaks5mo

    Score: 93/100 · 2 sections analyzed

Signed by skilld at 20324d2. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 2 days ago.

Activeupdated 3 months ago
Other metadata
compatibility
Targets WordPress 7.0+ (PHP 7.4.0+). Filesystem-based agent with bash + node. Some workflows require WP-CLI.
  • wordpress
  • interactivity-api
  • data-wp
  • php
  • javascript
  • hydration
  • directives
  • state-management
  • block-development

README badge

README badge for wordpress/agent-skills/wp-interactivity-api

Implements WordPress Interactivity API directives (data-wp-*, store/state/actions), block viewScriptModule integration, and server-side rendering with wp_interactivity_*() functions for WordPress 6.9+. Use this skill when building or debugging interactive blocks, themes, or plugins that rely on hydration, directive behavior, or state management via @wordpress/interactivity.

Generated from the current SKILL.md.

Does this skill work with WordPress versions before 6.9?
The skill targets WordPress 6.9+ (PHP 7.2.24+). Earlier versions do not have the Interactivity API.
What build tools does this skill support?
It works with @wordpress/scripts and custom bundlers (webpack/vite) that output ES modules. Some workflows require WP-CLI.
Can I use this skill to debug why directives aren't firing?
Yes. The skill includes a debugging procedure that checks if the view script module is loaded, the DOM element has data-wp-interactive, the store namespace matches, and there are no JS errors before hydration.
Does this skill cover server-side rendering of interactive blocks?
Yes. It includes procedures for pre-rendering HTML with correct initial state using wp_interactivity_state(), wp_interactivity_data_wp_context(), and wp_interactivity_process_directives() to ensure seamless hydration.
What changed in WordPress 6.9 that affects this skill?
data-wp-ignore is now deprecated, unique directive IDs now use the --- separator for multiple directives on one element, and getServerState()/getServerContext() reset between page transitions.

Generated from the current SKILL.md. These answers refresh after source changes.