Claude Skill

wp-interactivity-api

Build or audit interactive WordPress blocks with the Interactivity API: block.json interactivity support, viewScriptModule, PHP state/config/context helpers, data-wp-* directives, @wordpress/interactivity stores, hydration, async actions, and WordPress 7.1 binding rules. Use for

LLM Mart · 0 points · 0 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download lonsdale201-wp-agent-skills-wordpress_wp-interactivity-api-52f6020.zip · 5 KB
Part of lonsdale201/wp-agent-skills — 226 skills

Install

skills CLI npx skills add https://github.com/Lonsdale201/wp-agent-skills/tree/main/wordpress/wp-interactivity-api
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install lonsdale201-wp-agent-skills@llmmart
Git git clone https://github.com/Lonsdale201/wp-agent-skills.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole lonsdale201/wp-agent-skills collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

WordPress Interactivity API

Use WordPress's block-oriented reactive runtime when frontend elements need shared state, declarative DOM updates, or server-rendered markup that hydrates without changing. Do not use it merely to enqueue an unrelated JavaScript widget.

Choose the right mechanism

Requirement Prefer
One isolated click handler with no block integration A small viewScript or viewScriptModule
Reactive state, directives, or communication between blocks Interactivity API
Editor inspector/sidebar UI @wordpress/data and Block Editor packages
Data persistence or privileged work REST API with authorization; Interactivity API is only the UI/runtime layer

Minimal block contract

Declare support and load a script module through block metadata:

{
  "apiVersion": 3,
  "name": "acme/counter",
  "supports": { "interactivity": true },
  "render": "file:./render.php",
  "viewScriptModule": "file:./view.js"
}

In render.php, initialize public state and emit directives safely:

<?php
wp_interactivity_state(
    'acme/counter',
    array( 'total' => 0 )
);

$context = array( 'count' => (int) ( $attributes['start'] ?? 0 ) );
?>
<div
    data-wp-interactive="acme/counter"
    <?php echo wp_interactivity_data_wp_context( $context ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Core returns a complete escaped attribute. ?>
>
    <output data-wp-text="context.count"></output>
    <button type="button" data-wp-on--click="actions.increment">
        <?php esc_html_e( 'Increase', 'acme' ); ?>
    </button>
</div>

In view.js:

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

store( 'acme/counter', {
	actions: {
		increment() {
			const context = getContext();
			context.count += 1;
		},
	},
} );

Use wp_register_script_module() only for modules not already registered through block.json. Do not enqueue the module as a classic script.

State, context, and config

  • wp_interactivity_state( $namespace, $state ) defines store state shared by that namespace and recursively merges later calls.
  • data-wp-context is local to an element subtree; use wp_interactivity_data_wp_context() instead of hand-building JSON attributes.
  • wp_interactivity_config( $namespace, $config ) supplies immutable client configuration.
  • wp_interactivity_get_context() and wp_interactivity_get_element() are meaningful only while the server is processing directives.
  • State, context, and config reach the browser. Never place credentials, private tokens, capability-only data, or unfiltered personal data in them.

Directive rules

Common directives are:

  • data-wp-interactive="namespace" establishes the store namespace.
  • data-wp-on--click="actions.name" attaches an event action.
  • data-wp-bind--hidden="state.isHidden" binds an HTML attribute.
  • data-wp-class--is-open="context.isOpen" toggles a class.
  • data-wp-style--width="state.width" updates one style property.
  • data-wp-text="state.label" updates text content.
  • data-wp-init runs at element initialization; data-wp-watch reacts to accessed state.
  • Explicit cross-store references use namespace::state.path or namespace::actions.name.

Do not invent directive names or duplicate an attribute on the same element. When generating or modifying markup, prefer WP_HTML_Tag_Processor over regex or concatenation.

WordPress 7.1 behavior to audit

Server-side data-wp-bind now aligns more closely with the value sent to the client:

  • strings and booleans remain scalar;
  • numbers are JSON-formatted;
  • an object is resolved through its JSON representation;
  • arrays, non-scalar object results, and non-finite numbers are rejected with _doing_it_wrong() and the binding is treated as null.

Therefore, derived state used by an attribute binding must resolve to a finite scalar or null. Do not bind an array to class, style, or another attribute and rely on PHP coercion.

Malformed directive names and missing namespaces are also handled more defensively in 7.1. Treat notices under WP_DEBUG as contract failures, not harmless noise.

Async and external callbacks

Actions invoked by the runtime receive the correct scope. If an action continues in a timer, subscription, or other external callback, preserve scope with withScope(). Follow the package's generator-based async-action pattern where the installed WordPress version requires it; do not replace it blindly with an unscoped Promise callback.

Security and performance checklist

  • Keep authorization in the REST/AJAX endpoint. A hidden button or action name is not access control.
  • Escape ordinary PHP output and use the context helper for JSON attributes.
  • Keep state serializable and minimal; large repeated payloads increase HTML and hydration cost.
  • Use context for per-instance state and store state for genuinely shared data.
  • Make initial PHP output match the first client render to avoid hydration flicker.
  • Avoid global DOM queries when getElement() gives the scoped element.
  • Test with multiple instances of the block and with a full-page cache.
  • If client-side navigation is enabled, test mount, navigation, and teardown; do not assume a full page load resets module globals.

Verification

  1. Inspect the rendered page for the data-wp-* attributes and the script module.
  2. Enable WP_DEBUG and SCRIPT_DEBUG; resolve _doing_it_wrong() and console warnings.
  3. Confirm the server-rendered value equals the hydrated value before interaction.
  4. Exercise keyboard behavior and ARIA state, not only pointer clicks.
  5. Test two block instances to expose accidental global state.
  6. Test the oldest supported WordPress version; guard the feature if it predates 6.5.

Read references/contracts-and-debugging.md for directive value semantics, lifecycle choices, and failure modes.

Related skills

  • wordpress/wp-rest-api for authenticated persistence and endpoint contracts.
  • wordpress/wp-html-api for safe server-side directive mutation.
  • wordpress/wp-block-editor-iframe-compatibility for editor-canvas code.
  • plugin-scaffold/wp-plugin-assets-loading for script-module and asset loading.

References

Files (wp-agent-skills)
  • agents
    • openai.yaml 118 B
      interface:
        display_name: "WP Interactivity API"
        short_description: "Build and audit interactive WordPress blocks"
      
  • references
    • contracts-and-debugging.md 2.7 KB
      # Interactivity API contracts and debugging
      
      ## Server/client ownership
      
      | Concern | Server | Client |
      |---|---|---|
      | Shared initial data | `wp_interactivity_state()` | `state` from `store()` |
      | Per-element data | `data-wp-context` helper | `getContext()` |
      | Immutable settings | `wp_interactivity_config()` | `getConfig()` |
      | DOM behavior declaration | `data-wp-*` attributes | runtime directive processors |
      | Actions/callbacks | reference only | `store( namespace, { actions, callbacks } )` |
      
      State and configuration are serialized to the page. Treat both as public output.
      
      ## Directive value expectations
      
      - `data-wp-text` is for text, not trusted HTML.
      - `data-wp-class--name` and `data-wp-style--property` are focused boolean/value bindings.
      - `data-wp-bind--attribute` must resolve to a value meaningful as one HTML attribute.
      - `null` represents no bound attribute value.
      - WordPress 7.1 rejects non-scalar and non-finite attribute-binding results instead of letting PHP stringify them unpredictably.
      
      For complex class or style maps, use multiple class/style directives or a deliberate serialized scalar; do not bind an arbitrary array.
      
      ## Lifecycle choice
      
      - Use `data-wp-init` for initialization tied to an element's presence.
      - Use `data-wp-watch` when the callback intentionally reads reactive values and must rerun when those values change.
      - Use event directives for user/browser events.
      - Keep side effects out of derived state getters; a getter may be evaluated more than once.
      - Scope external callbacks with `withScope()` when they later call `getContext()` or `getElement()`.
      
      ## Failure diagnosis
      
      | Symptom | Check |
      |---|---|
      | No interaction | `supports.interactivity`, `viewScriptModule`, module asset metadata, exact namespace |
      | Correct after click but wrong initially | PHP state/context and server directive processing |
      | One instance updates another | shared store state used where per-instance context was needed |
      | Context undefined in timer | missing `withScope()` |
      | 7.1 `_doing_it_wrong()` from bind processor | array/object/non-finite result bound to an attribute |
      | Works on first load only | module-global lifecycle assumption under client-side navigation |
      | Markup breaks with quotes or `<` | hand-built JSON attribute instead of the context helper/tag processor |
      
      ## Compatibility gates
      
      The Interactivity API entered Core in WordPress 6.5 and evolved afterward. If supporting older Core:
      
      ```php
      if ( ! function_exists( 'wp_interactivity_state' ) ) {
          // Render a non-interactive fallback or load a separately maintained implementation.
          return;
      }
      ```
      
      Feature-detect the exact PHP function or JS export you use. A broad version comparison is less reliable when Gutenberg may provide newer editor packages than Core.
      
  • SKILL.md 7.1 KB
    ---
    name: wp-interactivity-api
    description: "Build or audit interactive WordPress blocks with the Interactivity API: block.json interactivity support, viewScriptModule, PHP state/config/context helpers, data-wp-* directives, @wordpress/interactivity stores, hydration, async actions, and WordPress 7.1 binding rules. Use for reactive frontend blocks, shared block state, server-rendered interactive markup, client-side navigation compatibility, or debugging server/client mismatches."
    license: GPLv2-or-later
    metadata:
      wp-skills-author: "Soczó Kristóf"
      wp-skills-contact: "mailto:lonsdale201@hotmail.com"
      wp-skills-plugin: "wordpress"
      wp-skills-plugin-version-tested: "6.5 - 7.1"
      wp-skills-wp-version-tested: "7.1"
      wp-skills-php-min: "7.4"
      wp-skills-last-updated: "2026-08-20"
    ---
    
    # WordPress Interactivity API
    
    Use WordPress's block-oriented reactive runtime when frontend elements need shared state, declarative DOM updates, or server-rendered markup that hydrates without changing. Do not use it merely to enqueue an unrelated JavaScript widget.
    
    ## Choose the right mechanism
    
    | Requirement | Prefer |
    |---|---|
    | One isolated click handler with no block integration | A small `viewScript` or `viewScriptModule` |
    | Reactive state, directives, or communication between blocks | Interactivity API |
    | Editor inspector/sidebar UI | `@wordpress/data` and Block Editor packages |
    | Data persistence or privileged work | REST API with authorization; Interactivity API is only the UI/runtime layer |
    
    ## Minimal block contract
    
    Declare support and load a script module through block metadata:
    
    ```json
    {
      "apiVersion": 3,
      "name": "acme/counter",
      "supports": { "interactivity": true },
      "render": "file:./render.php",
      "viewScriptModule": "file:./view.js"
    }
    ```
    
    In `render.php`, initialize public state and emit directives safely:
    
    ```php
    <?php
    wp_interactivity_state(
        'acme/counter',
        array( 'total' => 0 )
    );
    
    $context = array( 'count' => (int) ( $attributes['start'] ?? 0 ) );
    ?>
    <div
        data-wp-interactive="acme/counter"
        <?php echo wp_interactivity_data_wp_context( $context ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Core returns a complete escaped attribute. ?>
    >
        <output data-wp-text="context.count"></output>
        <button type="button" data-wp-on--click="actions.increment">
            <?php esc_html_e( 'Increase', 'acme' ); ?>
        </button>
    </div>
    ```
    
    In `view.js`:
    
    ```js
    import { getContext, store } from '@wordpress/interactivity';
    
    store( 'acme/counter', {
    	actions: {
    		increment() {
    			const context = getContext();
    			context.count += 1;
    		},
    	},
    } );
    ```
    
    Use `wp_register_script_module()` only for modules not already registered through `block.json`. Do not enqueue the module as a classic script.
    
    ## State, context, and config
    
    - `wp_interactivity_state( $namespace, $state )` defines store state shared by that namespace and recursively merges later calls.
    - `data-wp-context` is local to an element subtree; use `wp_interactivity_data_wp_context()` instead of hand-building JSON attributes.
    - `wp_interactivity_config( $namespace, $config )` supplies immutable client configuration.
    - `wp_interactivity_get_context()` and `wp_interactivity_get_element()` are meaningful only while the server is processing directives.
    - State, context, and config reach the browser. Never place credentials, private tokens, capability-only data, or unfiltered personal data in them.
    
    ## Directive rules
    
    Common directives are:
    
    - `data-wp-interactive="namespace"` establishes the store namespace.
    - `data-wp-on--click="actions.name"` attaches an event action.
    - `data-wp-bind--hidden="state.isHidden"` binds an HTML attribute.
    - `data-wp-class--is-open="context.isOpen"` toggles a class.
    - `data-wp-style--width="state.width"` updates one style property.
    - `data-wp-text="state.label"` updates text content.
    - `data-wp-init` runs at element initialization; `data-wp-watch` reacts to accessed state.
    - Explicit cross-store references use `namespace::state.path` or `namespace::actions.name`.
    
    Do not invent directive names or duplicate an attribute on the same element. When generating or modifying markup, prefer `WP_HTML_Tag_Processor` over regex or concatenation.
    
    ## WordPress 7.1 behavior to audit
    
    Server-side `data-wp-bind` now aligns more closely with the value sent to the client:
    
    - strings and booleans remain scalar;
    - numbers are JSON-formatted;
    - an object is resolved through its JSON representation;
    - arrays, non-scalar object results, and non-finite numbers are rejected with `_doing_it_wrong()` and the binding is treated as `null`.
    
    Therefore, derived state used by an attribute binding must resolve to a finite scalar or `null`. Do not bind an array to `class`, `style`, or another attribute and rely on PHP coercion.
    
    Malformed directive names and missing namespaces are also handled more defensively in 7.1. Treat notices under `WP_DEBUG` as contract failures, not harmless noise.
    
    ## Async and external callbacks
    
    Actions invoked by the runtime receive the correct scope. If an action continues in a timer, subscription, or other external callback, preserve scope with `withScope()`. Follow the package's generator-based async-action pattern where the installed WordPress version requires it; do not replace it blindly with an unscoped Promise callback.
    
    ## Security and performance checklist
    
    - Keep authorization in the REST/AJAX endpoint. A hidden button or action name is not access control.
    - Escape ordinary PHP output and use the context helper for JSON attributes.
    - Keep state serializable and minimal; large repeated payloads increase HTML and hydration cost.
    - Use context for per-instance state and store state for genuinely shared data.
    - Make initial PHP output match the first client render to avoid hydration flicker.
    - Avoid global DOM queries when `getElement()` gives the scoped element.
    - Test with multiple instances of the block and with a full-page cache.
    - If client-side navigation is enabled, test mount, navigation, and teardown; do not assume a full page load resets module globals.
    
    ## Verification
    
    1. Inspect the rendered page for the `data-wp-*` attributes and the script module.
    2. Enable `WP_DEBUG` and `SCRIPT_DEBUG`; resolve `_doing_it_wrong()` and console warnings.
    3. Confirm the server-rendered value equals the hydrated value before interaction.
    4. Exercise keyboard behavior and ARIA state, not only pointer clicks.
    5. Test two block instances to expose accidental global state.
    6. Test the oldest supported WordPress version; guard the feature if it predates 6.5.
    
    Read [references/contracts-and-debugging.md](references/contracts-and-debugging.md) for directive value semantics, lifecycle choices, and failure modes.
    
    ## Related skills
    
    - `wordpress/wp-rest-api` for authenticated persistence and endpoint contracts.
    - `wordpress/wp-html-api` for safe server-side directive mutation.
    - `wordpress/wp-block-editor-iframe-compatibility` for editor-canvas code.
    - `plugin-scaffold/wp-plugin-assets-loading` for script-module and asset loading.
    
    ## References
    
    - Read `references/contracts-and-debugging.md` for the directive table, store contract and hydration-mismatch debugging.
    - WordPress 7.1 Field Guide: <https://make.wordpress.org/core/2026/08/05/wordpress-7-1-field-guide/>
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related