WordPress

Getting Started with the WordPress Abilities API: A Practical Guide

Getting Started with the WordPress Abilities API: A Practical Guide

Understanding the WordPress Abilities API Ecosystem

Introduced in WordPress 6.9, the Abilities API establishes a shared language that allows WordPress core and third-party plugins to expose their functionality in a unified, structured format. Rather than isolating plugin features within self-contained admin interfaces or custom REST endpoints, the Abilities API enables site capabilities to be discovered and acted upon by both internal components and external entities. These external entities include AI models, automation platforms like Zapier or Make.com, server integrations communicating over the MCP protocol, and GitHub Actions CI/CD pipelines.

Through this architectural shift, WordPress evolves from a traditional content management system into a distributed execution engine. By standardizing functionality into an accessible catalog, external systems can orchestrate site operations remotely without custom API wrappers for every individual plugin feature.

The Structure and Mechanics of an Ability Contract

An ability serves as a formal contract between underlying PHP logic and any authorized entity requesting its execution. Every registered ability defines its input data requirements, its intent, and the schema of its return value. When an external or internal agent attempts to trigger an operation, WordPress serves as a gateway by verifying authentication and checking that incoming payload data strictly conforms to the established contract.

The contract model relies on several core elements when an ability is registered:

  • category: The functional group to which the ability belongs.
  • label and description: Human and machine-readable text outlining the ability’s purpose.
  • input_schema: A JSON Schema object defining required parameters, allowed data types, and properties.
  • output_schema: A JSON Schema object specifying the structured data returned upon execution.
  • execute_callback: The specific PHP function or method triggered when the ability runs.
  • permission_callback: A callback that evaluates whether the requesting user or agent holds adequate permissions.
  • meta: Metadata array containing REST exposure flags (show_in_rest) and operational annotations such as readonly, destructive, and idempotent.

If an incoming request fails permission verification or violates the input_schema, WordPress rejects the execution request before invoking the underlying PHP callback.

Discovering and Inspecting Abilities via WP-CLI

The Abilities API provides dedicated functions to list and retrieve ability definitions across a site installation. You can interact with these functions directly using WP-CLI terminal commands.

To list all registered abilities on a site, use wp_get_abilities() within wp eval:

wp eval '$abilities = wp_get_abilities(); foreach ( $abilities as $a ) { echo $a->get_name() . PHP_EOL; }'

On a default installation, this command outputs built-in core abilities:

core/get-site-info
core/get-user-info
core/get-environment-info

To inspect properties such as category, label, and description across registered abilities, you can loop through the full objects returned by wp_get_abilities():

wp eval ' 
$all_abilities = wp_get_abilities(); 
foreach ( $all_abilities as $ability ) { 
    echo "Ability Name: " . esc_html( $ability->get_name() ) . "n"; 
    echo "Label: " . esc_html( $ability->get_label() ) . "n"; 
    echo "Category: " . esc_html( $ability->get_category() ) . "n"; 
    echo "Description: " . esc_html( $ability->get_description() ) . "n"; 
    echo "---n"; 
}'

To view the exact schema and metadata for an individual ability, invoke wp_get_ability() with the ability’s registered slug:

wp eval ' 
$ability = wp_get_ability( "core/get-site-info" ); 
if ( ! $ability ) { 
    echo "Ability not foundn"; 
    exit( 1 ); 
} 
echo "Name: " . $ability->get_name() . "n"; 
echo "Label: " . $ability->get_label() . "n"; 
echo "Category: " . $ability->get_category() . "n"; 
echo "Description: " . $ability->get_description() . "n"; 
echo "nInput Schema:n"; 
var_dump( $ability->get_input_schema() ); 
echo "nOutput Schema:n"; 
var_dump( $ability->get_output_schema() ); 
echo "nMeta:n"; 
var_dump( $ability->get_meta() ); 
'

Verifying Ability Registration and Agent Permissions

Before triggering an execution step programmatically, application logic should verify that the targeted ability exists and that the current context maintains authorization.

You can verify whether an ability is registered using wp_has_ability():

wp eval ' 
if ( wp_has_ability( "core/get-site-info" ) ) { 
    echo "✓ core/get-site-info is registeredn"; 
} else { 
    echo "✗ core/get-site-info not foundn"; 
}'

To check permissions, call the check_permissions() method directly on the retrieved ability object. This method returns true if authorized, false if unauthorized, or a WP_Error object if the check encounters an evaluation failure:

wp --user=1 eval ' 
$ability = wp_get_ability( "core/get-site-info" ); 
if ( $ability ) { 
    $has_permissions = $ability->check_permissions(); 
    if ( true === $has_permissions ) { 
        echo "You have permissions to execute this ability."; 
    } else { 
        if ( is_wp_error( $has_permissions ) ) { 
            error_log( "Permissions check failed: " . $has_permissions->get_error_message() ); 
        } 
        echo "You do not have permissions to execute this ability."; 
    } 
} else { 
    echo "Ability not found."; 
}'

Registering Custom Ability Categories and Ability Contracts

Integrating custom plugin functionality into the central registry requires registering a category first, followed by registering the ability contract itself. Category registration takes place on the wp_abilities_api_categories_init action hook, while ability registration takes place on the wp_abilities_api_init action hook.

First, register a custom category using wp_register_ability_category():

function aicb_register_ability_category(): void { 
    if ( ! function_exists( 'wp_register_ability_category' ) ) { 
        return; 
    } 
    wp_register_ability_category( 
        'content-generation', 
        array( 
            'label'       => 'Content Generation', 
            'description' => 'AI-powered content transformation and structuring abilities', 
        ) 
    ); 
} 
add_action( 'wp_abilities_api_categories_init', 'aicb_register_ability_category' );

Next, define the ability contract and register it using wp_register_ability(). The example below defines an ability named ai-content-builder/audio-to-gutenberg-blocks, which accepts an audio media ID and outputs structured block objects:

function aicb_register_audio_to_gutenberg_blocks_ability(): void { 
    if ( ! function_exists( 'wp_register_ability' ) ) { 
        return; 
    } 

    $input_schema = array( 
        'type'       => 'object', 
        'properties' => array( 
            'audio_id' => array( 
                'type'        => 'integer', 
                'description' => 'The ID of the audio attachment to process and convert into Gutenberg blocks.', 
            ), 
        ), 
        'required'   => array( 'audio_id' ), 
    ); 

    $output_schema = array( 
        'type'       => 'object', 
        'properties' => array( 
            'title'      => array( 'type' => 'string' ), 
            'sections'   => array( 
                'type'  => 'array', 
                'items' => array( 'type' => 'object' ), 
            ), 
            'blocks'     => array( 
                'type'  => 'array', 
                'items' => array( 'type' => 'object' ), 
            ), 
            'transcript' => array( 'type' => 'string' ), 
        ), 
        'required'   => array( 'blocks' ), 
    ); 

    wp_register_ability( 
        'ai-content-builder/audio-to-gutenberg-blocks', 
        array( 
            'category'            => 'content-generation', 
            'label'               => 'Audio to Gutenberg Blocks', 
            'description'         => 'Transcribes audio and converts the content into WordPress Gutenberg-compatible block objects.', 
            'input_schema'        => $input_schema, 
            'output_schema'       => $output_schema, 
            'execute_callback'    => 'aicb_audio_to_gutenberg_blocks_callback', 
            'permission_callback' => static function (): bool { 
                return current_user_can( 'edit_posts' ); 
            }, 
            'meta'                => array( 
                'show_in_rest' => true, 
                'annotations'  => array( 
                    'readonly'     => false, 
                    'destructive'  => false, 
                    'idempotent'   => false, 
                    'instructions' => 'Processes an audio attachment: transcribes it, generates structured blog content via AI, and returns Gutenberg-ready block objects.', 
                ), 
            ), 
        ) 
    ); 
} 
add_action( 'wp_abilities_api_init', 'aicb_register_audio_to_gutenberg_blocks_ability' );

Implementing Callbacks, Data Normalization, and Block Conversion

The execution callback processes incoming input and returns the required payload format. In the audio conversion scenario, the callback coordinates processing, normalizes structured AI responses, and converts data into Gutenberg block descriptor objects.

First, the main execution callback receives input arguments, interacts with the processing service, normalizes content, and converts data to block descriptors:

function aicb_audio_to_gutenberg_blocks_callback( array $args ) { 
    $structured_json = wp_ai_client_prompt( $prompt ) 
        ->using_system_instruction( $instructions ) 
        ->using_temperature( 0.4 ) 
        ->as_json_response( $schema ) 
        ->generate_text(); 

    if ( is_wp_error( $structured_json ) ) { 
        return $structured_json; 
    } 

    $structured = json_decode( (string) $structured_json, true ); 
    if ( ! is_array( $structured ) ) { 
        return new WP_Error( 'invalid_ai_json', 'Could not parse structured AI response.', array( 'status' => 500 ) ); 
    } 

    $normalized = aicb_normalize_structured_post( $structured ); 
    if ( '' === $normalized['title'] && empty( $normalized['sections'] ) ) { 
        return new WP_Error( 'empty_structured_content', 'The AI provider returned empty structured content.', array( 'status' => 500 ) ); 
    } 

    $blocks = aicb_sections_to_blocks( $normalized['title'], $normalized['sections'] ); 
    return $blocks; 
}

Second, data normalization sanitizes text elements and validates section arrays:

function aicb_normalize_structured_post( array $structured ): array { 
    $title    = isset( $structured['title'] ) ? sanitize_text_field( (string) $structured['title'] ) : ''; 
    $sections = array(); 

    if ( isset( $structured['sections'] ) && is_array( $structured['sections'] ) ) { 
        foreach ( $structured['sections'] as $section ) { 
            if ( ! is_array( $section ) ) { 
                continue; 
            } 

            $heading       = isset( $section['heading'] ) ? sanitize_text_field( (string) $section['heading'] ) : ''; 
            $level         = 2; 
            $paragraphs    = array(); 
            if ( isset( $section['paragraphs'] ) && is_array( $section['paragraphs'] ) ) { 
                foreach ( $section['paragraphs'] as $paragraph ) { 
                    $clean_paragraph = trim( sanitize_textarea_field( (string) $paragraph ) ); 
                    if ( '' !== $clean_paragraph ) { 
                        $paragraphs[] = $clean_paragraph; 
                    } 
                } 
            } 

            $bullet_points = array(); 
            if ( isset( $section['bullet_points'] ) && is_array( $section['bullet_points'] ) ) { 
                foreach ( $section['bullet_points'] as $bullet_point ) { 
                    $clean_bullet_point = trim( sanitize_text_field( (string) $bullet_point ) ); 
                    if ( '' !== $clean_bullet_point ) { 
                        $bullet_points[] = $clean_bullet_point; 
                    } 
                } 
            } 

            if ( '' === $heading || empty( $paragraphs ) ) { 
                continue; 
            } 

            $sections[] = array( 
                'heading'       => $heading, 
                'level'         => $level, 
                'paragraphs'    => $paragraphs, 
                'bullet_points' => $bullet_points, 
            ); 
        } 
    } 

    return array( 
        'title'    => $title, 
        'sections' => $sections, 
    ); 
}

Third, normalized sections map directly to Block Descriptor objects matching core block representations rather than raw HTML markup strings. The core/list block expects HTML contained within its values attribute:

function aicb_sections_to_blocks( string $title, array $sections ): array { 
    $blocks = array(); 

    foreach ( $sections as $section ) { 
        if ( ! is_array( $section ) ) { 
            continue; 
        } 

        $heading = isset( $section['heading'] ) ? trim( (string) $section['heading'] ) : ''; 
        if ( '' === $heading ) { 
            continue; 
        } 

        $paragraphs = array(); 
        if ( isset( $section['paragraphs'] ) && is_array( $section['paragraphs'] ) ) { 
            foreach ( $section['paragraphs'] as $paragraph ) { 
                $clean = trim( (string) $paragraph ); 
                if ( '' !== $clean ) { 
                    $paragraphs[] = $clean; 
                } 
            } 
        } 

        if ( empty( $paragraphs ) ) { 
            continue; 
        } 

        $blocks[] = array( 
            'name'       => 'core/heading', 
            'attributes' => array( 
                'content' => $heading, 
                'level'   => 2, 
            ), 
        ); 

        foreach ( $paragraphs as $paragraph ) { 
            $blocks[] = array( 
                'name'       => 'core/paragraph', 
                'attributes' => array( 
                    'content' => $paragraph, 
                ), 
            ); 
        } 

        if ( isset( $section['bullet_points'] ) && is_array( $section['bullet_points'] ) ) { 
            $bullet_items_html = ''; 
            foreach ( $section['bullet_points'] as $bullet_point ) { 
                $clean_bullet = trim( sanitize_text_field( (string) $bullet_point ) ); 
                if ( '' === $clean_bullet ) { 
                    continue; 
                } 
                $bullet_items_html .= '<li>' . esc_html( $clean_bullet ) . '</li>'; 
            } 

            if ( '' !== $bullet_items_html ) { 
                $blocks[] = array( 
                    'name'       => 'core/list', 
                    'attributes' => array( 
                        'values' => '<ul>' . $bullet_items_html . '</ul>', 
                    ), 
                ); 
            } 
        } 
    } 

    return $blocks; 
}

Executing Abilities Programmatically and Parsing Outputs

To run a registered ability programmatically, call the execute() method on its ability object. You can pass required input arguments directly if specified by the input schema.

The following example executes the built-in core/get-site-info ability via WP-CLI:

wp --user=1 eval ' 
$ability = wp_get_ability( "core/get-site-info" ); 
if ( ! $ability ) { 
    echo "Ability not foundn"; 
    exit(1); 
} 
$result = $ability->execute(); 
if ( is_wp_error( $result ) ) { 
    echo "ERROR: " . $result->get_error_message() . "n"; 
    exit(1); 
} 
echo json_encode( $result, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES ) . "n"; 
'

Upon execution, the method returns the data structured according to the ability’s output contract:

{
    "name": "WordPress 7.0",
    "description": "",
    "url": "http://yoursite.kinsta.cloud",
    "wpurl": "http://yoursite.kinsta.cloud",
    "admin_email": "admin@example.com",
    "charset": "UTF-8",
    "language": "en-US",
    "version": "7.1-alpha-62550"
}

By registering custom callbacks and standardizing input/output schemas, developers ensure that internal operations remain completely interoperable, secure, and ready for modern external integrations.

Frequently asked questions

Which version of WordPress introduced the Abilities API?

The Abilities API was introduced in WordPress 6.9.

What standard core abilities are included in WordPress by default?

Default core abilities include core/get-site-info, core/get-user-info, and core/get-environment-info.

Which hooks are required to register ability categories and individual abilities?

Ability categories are registered using the wp_abilities_api_categories_init hook, while individual abilities are registered on the wp_abilities_api_init hook.

How does WordPress enforce safety and schema validation when triggering an ability?

WordPress checks permissions using the ability's permission_callback and validates all incoming payload arguments against the declared input_schema before routing execution to the PHP callback function.

Primary reference: Review the original announcement for exact release details. This article is an independent explanation and does not reproduce the source text.

Leave a Reply

Your email address will not be published. Required fields are marked *