How the Client-Side Abilities API works in WordPress 7.0

WordPress 7.0 is out, and it ships the Client-Side Abilities API. If you have ever written a custom AJAX handler or a throwaway REST endpoint just to fire a UI action, that is the pain this replaces. The API gives browser agents, AI tools and your own JavaScript one common way to reach logic registered on the server.

I have spent 14 years watching WordPress turn from a blogging tool into an application framework, and a standard way for JavaScript to call server-registered logic has been missing that entire time. It matters most for WebMCP and browser agent integration, where the agent has to ask a site what it can do instead of guessing.

The two-package split

The Client-Side Abilities API ships as two packages. The split matters if you watch payload size, because a small project does not have to pull in the whole core stack.

  • @wordpress/abilities: the state layer. Store, registration and execution logic, with no server dependencies at all. You can drop it into a non-WordPress project if an ability store is all you need.
  • @wordpress/core-abilities: the integration layer. It pulls the abilities registered on the server from the /wp-abilities/v1/ REST endpoint and hydrates the client store with them.

That second package is the one to watch if you build with WordPress and AI agents, since an agent can now discover what a given site is actually able to do.

Enqueuing with script modules

WordPress 7.0 leans hard on script modules, so you load this API with wp_enqueue_script_module. Enqueue the core integration package when you need the abilities that are registered server side.

add_action( 'admin_enqueue_scripts', 'bbioon_enqueue_abilities' );
function bbioon_enqueue_abilities() {
    // Enqueue the core integration for server-registered abilities
    wp_enqueue_script_module( '@wordpress/core-abilities' );
}

If you missed the earlier post on the server-side foundations, that is where I laid out why the Abilities API ends integration headaches.

Registering and validating abilities

An ability needs a category before you can register it. Categories behave like namespaces, which keeps the global store from turning into a pile of colliding names. Here is a client-side ability with strict validation on its input.

import { registerAbility, registerAbilityCategory } from '@wordpress/abilities';

// 1. Register the Category
registerAbilityCategory( 'my-plugin-tools', {
    label: 'Plugin Tools',
    description: 'Custom UI actions for my plugin',
} );

// 2. Register the Ability with Schema Validation
registerAbility( {
    name: 'my-plugin/update-layout',
    label: 'Update Layout',
    category: 'my-plugin-tools',
    input_schema: {
        type: 'object',
        properties: {
            layoutType: { type: 'string', enum: [ 'grid', 'list' ] },
        },
        required: [ 'layoutType' ],
    },
    callback: async ( { layoutType } ) => {
        console.log( `Switching to ${layoutType} view...` );
        return { success: true };
    },
} );

Validation runs against JSON Schema (Draft-04), and that is the part worth caring about. Hand a callback junk data and you get an ability_invalid_input error before your code runs, instead of the state pollution older React-based plugins are full of.

Execution and permissions

Calling an ability is easy. The error handling is the part people skip. Permissions and validation both fail at runtime, so wrap every executeAbility call in a try-catch.

import { executeAbility } from '@wordpress/abilities';

async function handleUIAction() {
    try {
        const result = await executeAbility( 'my-plugin/update-layout', {
            layoutType: 'grid'
        } );
        console.log( 'Action complete:', result.success );
    } catch ( error ) {
        if ( error.code === 'ability_permission_denied' ) {
            alert( 'You do not have permission for this.' );
        } else {
            console.error( 'Execution failed:', error.message );
        }
    }
}

For server-side abilities, the Client-Side Abilities API picks the HTTP method from the ability metadata. An ability marked readonly: true goes out as a GET. Destructive actions default to POST, or DELETE when they are marked idempotent.

If this Client-Side Abilities API work is eating your dev hours, hand it over to me. I have been wrestling with WordPress since the 4.x days, and I can help move your legacy code onto this architecture.

Where to start

Script modules plus formally registered abilities make WordPress 7.0 a steadier base than the AJAX-and-custom-endpoint approach it replaces. The split between @wordpress/abilities and @wordpress/core-abilities is what makes the interfaces testable, since the state layer runs with no server behind it. If you maintain a plugin with its own AJAX silo, that silo is the first thing worth registering as an ability.

author avatar
Ahmad Wael
I'm a WordPress and WooCommerce developer with 15+ years of experience building custom e-commerce solutions and plugins. I specialize in PHP development, following WordPress coding standards to deliver clean, maintainable code. Currently, I'm exploring AI and e-commerce by building multi-agent systems and SaaS products that integrate technologies like Google Gemini API with WordPress platforms, approaching every project with a commitment to performance, security, and exceptional user experience.