WordPress 6.9 introduces a feature that fundamentally changes how we think about extending WordPress functionality: the Abilities API. This new system allows developers to register discrete, self-documenting capabilities that can be discovered and executed by AI assistants, external tools, or programmatically within your own code.

In this comprehensive guide, I’ll walk you through everything you need to know about the Abilities API, from basic registration to advanced usage patterns, complete with real-world code examples.

What is the WordPress Abilities API?

The Abilities API is a standardized way to register custom functions (called “abilities”) in WordPress. Each ability is a self-contained unit of functionality with:

  • A unique identifier (namespaced slug)
  • Input schema defining what parameters it accepts
  • Output schema describing what it returns
  • Execute callback containing the actual logic
  • Permission callback for security control
  • Metadata for REST API exposure and discoverability

Think of it as creating a well-documented menu of actions your WordPress site can perform. Unlike traditional custom functions scattered throughout your codebase, abilities are discoverable, validated, and accessible through multiple channels.

Why the Abilities API Matters

AI Integration

The most compelling reason to adopt the Abilities API is AI integration. As AI assistants become more prevalent, having standardized, discoverable endpoints means your WordPress site can interact with AI tools without custom integration work.

Standardization

Every ability follows the same pattern with JSON Schema validation. This means predictable inputs, outputs, and error handling across your entire codebase.

Security

Built-in permission callbacks ensure abilities are only executed by authorized users. No more scattered capability checks throughout your code.

Discoverability

Abilities can be exposed via the REST API, making them accessible to external tools, mobile apps, or any HTTP client.

Getting Started: Registering Your First Ability

Let’s start with a practical example. We’ll create an ability that counts published posts for any post type.

Step 1: Register an Ability Category (Optional)

Categories help organize related abilities. Register them using the wp_abilities_api_categories_init hook:

PHP
function mytheme_register_ability_categories() {
    wp_register_ability_category(
        'site-utilities',
        array(
            'label'       => __( 'Site Utilities', 'mytheme' ),
            'description' => __( 'Administrative and content management tools', 'mytheme' ),
        )
    );
}
add_action( 'wp_abilities_api_categories_init', 'mytheme_register_ability_categories' );

Step 2: Register the Ability

Use the wp_abilities_api_init hook to register abilities:

PHP
function mytheme_register_abilities() {
    // Ensure the Abilities API exists (WP 6.9+)
    if ( ! function_exists( 'wp_register_ability' ) ) {
        return;
    }

    wp_register_ability(
        'mytheme/get-post-count',
        array(
            'label'               => __( 'Get Post Count', 'mytheme' ),
            'description'         => __( 'Returns the total number of published posts for a given post type.', 'mytheme' ),
            'category'            => 'site-utilities',
            'input_schema'        => array(
                'type'       => 'object',
                'properties' => array(
                    'post_type' => array(
                        'type'        => 'string',
                        'description' => __( 'Post type slug (e.g., post, page, product)', 'mytheme' ),
                    ),
                ),
                'required'   => array( 'post_type' ),
            ),
            'output_schema'       => array(
                'type'       => 'object',
                'properties' => array(
                    'post_type' => array(
                        'type'        => 'string',
                        'description' => __( 'Post type slug', 'mytheme' ),
                    ),
                    'count'     => array(
                        'type'        => 'integer',
                        'description' => __( 'Number of published posts', 'mytheme' ),
                    ),
                ),
            ),
            'execute_callback'    => 'mytheme_ability_get_post_count',
            'permission_callback' => 'mytheme_ability_permission_check',
            'meta'                => array(
                'show_in_rest' => true,
            ),
        )
    );
}
add_action( 'wp_abilities_api_init', 'mytheme_register_abilities' );

Step 3: Create the Execute Callback

This function contains your actual logic:

PHP
function mytheme_ability_get_post_count( $args ) {
    $post_type = sanitize_text_field( $args['post_type'] );

    // Validate post type exists
    if ( ! post_type_exists( $post_type ) ) {
        return array(
            'post_type' => $post_type,
            'count'     => 0,
        );
    }

    $counts = wp_count_posts( $post_type );

    return array(
        'post_type' => $post_type,
        'count'     => isset( $counts->publish ) ? (int) $counts->publish : 0,
    );
}

Step 4: Create the Permission Callback

Control who can execute the ability:

PHP
function mytheme_ability_permission_check() {
    // Require admin capabilities for this ability
    return current_user_can( 'manage_options' );
}

Three Ways to Execute Abilities

One of the most powerful aspects of the Abilities API is the flexibility in how abilities can be called. Let’s explore all three methods.

Method 1: PHP Code (Internal Execution)

Use this when calling abilities from within your theme or plugin code:

PHP
add_action( 'admin_init', function() {
    // Check if Abilities API exists
    if ( ! function_exists( 'wp_get_ability' ) ) {
        return;
    }

    // Get the ability instance
    $ability = wp_get_ability( 'mytheme/get-post-count' );

    if ( $ability ) {
        // Execute with parameters
        $result = $ability->execute( array(
            'post_type' => 'post',
        ) );

        // Use the result
        error_log( 'Post count: ' . $result['count'] );
    }
} );

This method is ideal for:

  • Cron jobs that need to perform ability actions
  • Admin dashboard widgets
  • Internal automation workflows
  • Testing abilities during development

Method 2: REST API (External HTTP Calls)

When show_in_rest is enabled in the ability meta, it becomes accessible via the WordPress REST API:

ShellScript
curl -X POST \
  -u "username:application_password" \
  -H "Content-Type: application/json" \
  -d '{
    "input": {
      "post_type": "post"
    }
  }' \
  https://yoursite.com/wp-json/wp-abilities/v1/abilities/mytheme/get-post-count/run

Response:

JSON
{
  "post_type": "post",
  "count": 42
}

This method is perfect for:

  • External applications integrating with WordPress
  • Mobile apps
  • Third-party services and webhooks
  • Automation tools like Zapier or Make
  • Command-line scripts

Authentication Options:

  • Application passwords (recommended)
  • OAuth
  • JWT tokens (with appropriate plugin)
  • Cookie authentication (for logged-in users)

Method 3: AI Natural Language (AI Assistant Integration)

This is where the Abilities API truly shines. AI assistants that understand the abilities schema can execute them through natural language:

Example Prompt:

Plaintext
Using the available ability, tell me how many pages are published on the site.
API credentials: username:application_password

The AI assistant will:

  1. Discover the available ability
  2. Understand its input requirements
  3. Make the appropriate API call
  4. Return human-readable results

This method enables:

  • Voice-controlled WordPress management
  • Chat-based content administration
  • AI-powered site analytics
  • Natural language automation

Advanced Example: Empty Trash Ability

Let’s look at a more complex ability that performs a destructive action—emptying the trash for a specific post type:

PHP
wp_register_ability(
    'mytheme/empty-trash',
    array(
        'label'               => __( 'Empty Trash', 'mytheme' ),
        'description'         => __( 'Permanently deletes all trashed posts for a given post type.', 'mytheme' ),
        'category'            => 'site-utilities',
        'input_schema'        => array(
            'type'       => 'object',
            'properties' => array(
                'post_type' => array(
                    'type'        => 'string',
                    'description' => __( 'Post type slug', 'mytheme' ),
                ),
            ),
            'required'   => array( 'post_type' ),
        ),
        'output_schema'       => array(
            'type'        => 'string',
            'description' => __( 'Success or error message', 'mytheme' ),
        ),
        'execute_callback'    => 'mytheme_ability_empty_trash',
        'permission_callback' => function() {
            // Require delete capabilities
            return current_user_can( 'delete_others_posts' );
        },
        'meta'                => array(
            'show_in_rest' => true,
        ),
    )
);

function mytheme_ability_empty_trash( $args ) {
    $post_type = sanitize_text_field( $args['post_type'] );

    if ( ! post_type_exists( $post_type ) ) {
        return __( 'Post type does not exist.', 'mytheme' );
    }

    $trashed_posts = get_posts( array(
        'post_type'      => $post_type,
        'post_status'    => 'trash',
        'posts_per_page' => -1,
        'fields'         => 'ids',
    ) );

    foreach ( $trashed_posts as $post_id ) {
        wp_delete_post( $post_id, true );
    }

    return sprintf(
        __( 'Successfully deleted %d trashed %s.', 'mytheme' ),
        count( $trashed_posts ),
        $post_type
    );
}

Best Practices

1. Always Use Permission Callbacks

Never set permission callbacks to return true unconditionally in production:

PHP
// DON'T do this in production
'permission_callback' => '__return_true',

// DO this instead
'permission_callback' => function() {
    return current_user_can( 'appropriate_capability' );
},

2. Validate and Sanitize Inputs

Even though the API validates against your schema, always sanitize within your callback:

PHP
function my_callback( $args ) {
    $post_type = sanitize_text_field( $args['post_type'] );
    $limit = absint( $args['limit'] ?? 10 );
    // ...
}

3. Use Descriptive Schemas

Your input and output schemas serve as documentation. Make them detailed:

PHP
'input_schema' => array(
    'type'       => 'object',
    'properties' => array(
        'post_type' => array(
            'type'        => 'string',
            'description' => 'The post type slug to query. Examples: post, page, product, event.',
            'enum'        => array( 'post', 'page' ), // Optional: restrict values
        ),
    ),
),

4. Handle Errors Gracefully

Return meaningful error messages:

PHP
function my_callback( $args ) {
    if ( ! post_type_exists( $args['post_type'] ) ) {
        return new WP_Error(
            'invalid_post_type',
            __( 'The specified post type does not exist.', 'mytheme' ),
            array( 'status' => 400 )
        );
    }
    // ...
}

5. Namespace Your Abilities

Always use a namespace prefix to avoid conflicts:

Plaintext
// Good
'mytheme/get-post-count'
'myplugin/send-notification'

// Bad
'get-post-count'
'send-notification'

Debugging and Testing

Check if Abilities API is Active

PHP
add_action( 'wp_abilities_api_init', function() {
    error_log( 'Abilities API initialized successfully' );
} );

List All Registered Abilities

PHP
add_action( 'admin_init', function() {
    if ( function_exists( 'wp_get_abilities' ) ) {
        $abilities = wp_get_abilities();
        error_log( print_r( $abilities, true ) );
    }
} );

Test via REST API

Use tools like Postman, Insomnia, or curl to test your abilities before integrating them into applications.

Conclusion

The WordPress Abilities API represents a significant step forward in how we build and extend WordPress. By providing a standardized way to register discoverable, secure, and self-documenting functions, it opens the door to seamless AI integration and external tool connectivity.

Whether you’re building themes, plugins, or custom WordPress applications, adopting the Abilities API now will future-proof your code for the AI-native web.

Start simple with read-only abilities like post counts or site statistics, then gradually add more complex functionality as you become comfortable with the patterns.

The sites and plugins that embrace this API early will have a significant advantage as AI assistants become the primary way users interact with their WordPress installations.