Make WordPress Core

Changeset 61032


Ignore:
Timestamp:
10/21/2025 01:50:11 PM (8 weeks ago)
Author:
gziolo
Message:

Abilities API: Introduce server-side registry and REST API endpoints

Feature proposal at https://make.wordpress.org/ai/2025/07/17/abilities-api/.
Project developed in https://github.com/WordPress/abilities-api.

Introduces a new Abilities API that allows WordPress plugins and themes to register and execute custom abilities with built-in permission checking, input/output validation via JSON Schema, and REST API integration.

## Public Functions

### Ability Management

  • wp_register_ability( string $name, array $args ): ?WP_Ability - Registers a new ability (must be called on wp_abilities_api_init hook)
  • wp_unregister_ability( string $name ): ?WP_Ability - Unregisters an ability
  • wp_has_ability( string $name ): bool - Checks if an ability is registered
  • wp_get_ability( string $name ): ?WP_Ability - Retrieves a registered ability
  • wp_get_abilities(): array - Retrieves all registered abilities

### Ability Category Management

  • wp_register_ability_category( string $slug, array $args ): ?WP_Ability_Category - Registers an ability category (must be called on wp_abilities_api_categories_init hook)
  • wp_unregister_ability_category( string $slug ): ?WP_Ability_Category - Unregisters an ability category
  • wp_has_ability_category( string $slug ): bool - Checks if an ability category is registered
  • wp_get_ability_category( string $slug ): ?WP_Ability_Category - Retrieves a registered ability category
  • wp_get_ability_categories(): array - Retrieves all registered ability categories

## Public Classes

  • WP_Ability - Encapsulates ability properties and methods (execute, check_permission, validate_input, etc.)
  • WP_Ability_Category - Encapsulates ability category properties
  • WP_Abilities_Registry - Manages ability registration and lookup (private, accessed via functions)
  • WP_Ability_Categories_Registry - Manages ability category registration (private, accessed via functions)
  • WP_REST_Abilities_V1_List_Controller - REST controller for listing abilities
  • WP_REST_Abilities_V1_Run_Controller - REST controller for executing abilities

## REST API Endpoints

### Namespace: wp-abilities/v1

#### List Abilities

  • GET /wp-abilities/v1/abilities - Retrieve all registered abilities
    • Query parameters: page, per_page, category

#### Get Single Ability

  • GET /wp-abilities/v1/abilities/(?P<name>[a-zA-Z0-9\-\/]+) - Retrieve a specific ability by name

#### Execute Ability

  • GET|POST|DELETE /wp-abilities/v1/abilities/(?P<name>[a-zA-Z0-9\-\/]+)/run - Execute an ability
    • Supports multiple HTTP methods based on ability annotations
    • Validates input against ability's input schema
    • Validates output against ability's output schema
    • Performs permission checks via ability's permission callback

## Hooks

### Actions

  • wp_abilities_api_categories_init - Fired when ability categories registry is initialized (register categories here)
  • wp_abilities_api_init - Fired when abilities registry is initialized (register abilities here)
  • wp_before_execute_ability - Fired before an ability gets executed, after input validation and permissions check
  • wp_after_execute_ability - Fires immediately after an ability finished executing

### Filters

  • wp_register_ability_category_args - Filters ability category arguments before registration
  • wp_register_ability_args - Filters ability arguments before registration

Developed in https://github.com/WordPress/wordpress-develop/pull/9410.

Props gziolo, jorbin, justlevine, westonruter, jason_the_adams, flixos90, karmatosed, timothyblynjacobs.
Fixes #64098.

Location:
trunk
Files:
16 added
4 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/rest-api.php

    r61029 r61032  
    484484    $font_collections_controller = new WP_REST_Font_Collections_Controller();
    485485    $font_collections_controller->register_routes();
     486
     487    // Abilities.
     488    $abilities_run_controller  = new WP_REST_Abilities_V1_Run_Controller();
     489    $abilities_run_controller->register_routes();
     490    $abilities_list_controller = new WP_REST_Abilities_V1_List_Controller();
     491    $abilities_list_controller->register_routes();
    486492}
    487493
  • trunk/src/wp-settings.php

    r61029 r61032  
    286286require ABSPATH . WPINC . '/admin-bar.php';
    287287require ABSPATH . WPINC . '/class-wp-application-passwords.php';
     288require ABSPATH . WPINC . '/abilities-api/class-wp-ability-category.php';
     289require ABSPATH . WPINC . '/abilities-api/class-wp-ability-categories-registry.php';
     290require ABSPATH . WPINC . '/abilities-api/class-wp-ability.php';
     291require ABSPATH . WPINC . '/abilities-api/class-wp-abilities-registry.php';
     292require ABSPATH . WPINC . '/abilities-api.php';
    288293require ABSPATH . WPINC . '/rest-api.php';
    289294require ABSPATH . WPINC . '/rest-api/class-wp-rest-server.php';
     
    332337require ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-font-faces-controller.php';
    333338require ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-font-collections-controller.php';
     339require ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-abilities-v1-list-controller.php';
     340require ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-abilities-v1-run-controller.php';
    334341require ABSPATH . WPINC . '/rest-api/fields/class-wp-rest-meta-fields.php';
    335342require ABSPATH . WPINC . '/rest-api/fields/class-wp-rest-comment-meta-fields.php';
  • trunk/tests/phpunit/tests/rest-api/rest-schema-setup.php

    r61029 r61032  
    204204            '/wp/v2/font-families/(?P<font_family_id>[\d]+)/font-faces/(?P<id>[\d]+)',
    205205            '/wp/v2/font-families/(?P<id>[\d]+)',
     206            '/wp-abilities/v1',
     207            '/wp-abilities/v1/abilities/(?P<name>[a-zA-Z0-9\-\/]+?)/run',
     208            '/wp-abilities/v1/abilities/(?P<name>[a-zA-Z0-9\-\/]+)',
     209            '/wp-abilities/v1/abilities',
    206210        );
    207211
     
    214218            preg_match( '#^/oembed/1\.0(/.+)?$#', $route ) ||
    215219            preg_match( '#^/wp/v2(/.+)?$#', $route ) ||
    216             preg_match( '#^/wp-site-health/v1(/.+)?$#', $route )
     220            preg_match( '#^/wp-site-health/v1(/.+)?$#', $route ) ||
     221            preg_match( '#^/wp-abilities/v1(/.+)?$#', $route )
    217222        );
    218223    }
  • trunk/tests/qunit/fixtures/wp-api-generated.js

    r61029 r61032  
    2020        "wp/v2",
    2121        "wp-site-health/v1",
    22         "wp-block-editor/v1"
     22        "wp-block-editor/v1",
     23        "wp-abilities/v1"
    2324    ],
    2425    "authentication": {
     
    1348213483                }
    1348313484            ]
     13485        },
     13486        "/wp-abilities/v1": {
     13487            "namespace": "wp-abilities/v1",
     13488            "methods": [
     13489                "GET"
     13490            ],
     13491            "endpoints": [
     13492                {
     13493                    "methods": [
     13494                        "GET"
     13495                    ],
     13496                    "args": {
     13497                        "namespace": {
     13498                            "default": "wp-abilities/v1",
     13499                            "required": false
     13500                        },
     13501                        "context": {
     13502                            "default": "view",
     13503                            "required": false
     13504                        }
     13505                    }
     13506                }
     13507            ],
     13508            "_links": {
     13509                "self": [
     13510                    {
     13511                        "href": "http://example.org/index.php?rest_route=/wp-abilities/v1"
     13512                    }
     13513                ]
     13514            }
     13515        },
     13516        "/wp-abilities/v1/abilities/(?P<name>[a-zA-Z0-9\\-\\/]+?)/run": {
     13517            "namespace": "wp-abilities/v1",
     13518            "methods": [
     13519                "GET",
     13520                "POST",
     13521                "PUT",
     13522                "PATCH",
     13523                "DELETE"
     13524            ],
     13525            "endpoints": [
     13526                {
     13527                    "methods": [
     13528                        "GET",
     13529                        "POST",
     13530                        "PUT",
     13531                        "PATCH",
     13532                        "DELETE"
     13533                    ],
     13534                    "args": {
     13535                        "name": {
     13536                            "description": "Unique identifier for the ability.",
     13537                            "type": "string",
     13538                            "pattern": "^[a-zA-Z0-9\\-\\/]+$",
     13539                            "required": false
     13540                        },
     13541                        "input": {
     13542                            "description": "Input parameters for the ability execution.",
     13543                            "type": [
     13544                                "integer",
     13545                                "number",
     13546                                "boolean",
     13547                                "string",
     13548                                "array",
     13549                                "object",
     13550                                "null"
     13551                            ],
     13552                            "default": null,
     13553                            "required": false
     13554                        }
     13555                    }
     13556                }
     13557            ]
     13558        },
     13559        "/wp-abilities/v1/abilities": {
     13560            "namespace": "wp-abilities/v1",
     13561            "methods": [
     13562                "GET"
     13563            ],
     13564            "endpoints": [
     13565                {
     13566                    "methods": [
     13567                        "GET"
     13568                    ],
     13569                    "args": {
     13570                        "context": {
     13571                            "description": "Scope under which the request is made; determines fields present in response.",
     13572                            "type": "string",
     13573                            "enum": [
     13574                                "view",
     13575                                "embed",
     13576                                "edit"
     13577                            ],
     13578                            "default": "view",
     13579                            "required": false
     13580                        },
     13581                        "page": {
     13582                            "description": "Current page of the collection.",
     13583                            "type": "integer",
     13584                            "default": 1,
     13585                            "minimum": 1,
     13586                            "required": false
     13587                        },
     13588                        "per_page": {
     13589                            "description": "Maximum number of items to be returned in result set.",
     13590                            "type": "integer",
     13591                            "default": 50,
     13592                            "minimum": 1,
     13593                            "maximum": 100,
     13594                            "required": false
     13595                        },
     13596                        "category": {
     13597                            "description": "Limit results to abilities in specific ability category.",
     13598                            "type": "string",
     13599                            "required": false
     13600                        }
     13601                    }
     13602                }
     13603            ],
     13604            "_links": {
     13605                "self": [
     13606                    {
     13607                        "href": "http://example.org/index.php?rest_route=/wp-abilities/v1/abilities"
     13608                    }
     13609                ]
     13610            }
     13611        },
     13612        "/wp-abilities/v1/abilities/(?P<name>[a-zA-Z0-9\\-\\/]+)": {
     13613            "namespace": "wp-abilities/v1",
     13614            "methods": [
     13615                "GET"
     13616            ],
     13617            "endpoints": [
     13618                {
     13619                    "methods": [
     13620                        "GET"
     13621                    ],
     13622                    "args": {
     13623                        "name": {
     13624                            "description": "Unique identifier for the ability.",
     13625                            "type": "string",
     13626                            "pattern": "^[a-zA-Z0-9\\-\\/]+$",
     13627                            "required": false
     13628                        }
     13629                    }
     13630                }
     13631            ]
    1348413632        }
    1348513633    },
Note: See TracChangeset for help on using the changeset viewer.