-
Notifications
You must be signed in to change notification settings - Fork 50
Adding initial developer documentation #10
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
gziolo
merged 11 commits into
WordPress:trunk
from
jonathanbossenger:add/developer-documentation
Aug 19, 2025
Merged
Changes from all commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
8ba63e9
Adding initial developer documentation
jonathanbossenger a2f3729
Merge branch 'trunk' into add/developer-documentation
gziolo 475c54e
Manual review updates
jonathanbossenger 1fe2b76
Manual review updates
jonathanbossenger 54ab112
Updating array syntax to follow WPCS
jonathanbossenger 0d2d712
Updating example code to use named callback functions
jonathanbossenger d50328f
Merge branch 'trunk' into add/developer-documentation
jonathanbossenger 506d90f
Merge branch 'WordPress:trunk' into add/developer-documentation
jonathanbossenger c7fa8db
Review updates to intro
jonathanbossenger a168ff2
Review updates
jonathanbossenger e5b2502
Update code examples to use WP_Error object on failure
jonathanbossenger File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,67 @@ | ||
| # 1. Introduction & Overview | ||
|
|
||
| **What is the Abilities API?** | ||
|
|
||
| The WordPress Abilities API provides a standardized way to register and discover distinct units of functionality within a WordPress site. These units, called "Abilities", represent specific actions or capabilities that components can perform, with clearly defined inputs, outputs, and permissions. | ||
|
|
||
| It acts as a central registry, making it easier for different parts of WordPress, third-party plugins, themes, and external systems (like AI agents) to understand and interact with the capabilities available on a specific site. | ||
|
|
||
| **Core Concepts** | ||
|
|
||
| - **Ability:** A distinct piece of functionality with a unique name following the `namespace/ability-name` pattern. Each ability has a human-readable name and description, input/output definitions (using JSON Schema), optional permissions, and an associated callback function for execution. Each registered Ability is an instance of the `WP_Ability` class. | ||
| - **Registry:** A central, singleton object (`WP_Abilities_Registry`) that holds all registered abilities. It provides methods for registering, unregistering, finding, and querying abilities. | ||
| - **Callback:** The PHP function or method executed when an ability is called via `WP_Ability::execute()`. | ||
| - **Schema:** JSON Schema definitions for an ability's expected input (`input_schema`) and its returned output (`output_schema`). This allows for validation and helps agents understand how to use the ability. | ||
| - **Permission Callback:** An optional function that determines if the current user can execute a specific ability. | ||
| - **Namespace:** The first part of an ability name (before the slash), typically matching the plugin or component name that registers the ability. | ||
|
|
||
| **Goals and Benefits** | ||
|
|
||
| - **Standardization:** Provides a single, consistent way to expose site capabilities. | ||
| - **Discoverability:** Makes functionality easily discoverable by AI systems and automation tools. | ||
| - **Validation:** Built-in input/output validation using JSON Schema ensures data integrity. | ||
| - **Security:** Permission callbacks provide fine-grained access control. | ||
| - **Extensibility:** Simple registration pattern allows any plugin or theme to expose their capabilities. | ||
| - **AI-Friendly:** Machine-readable format enables intelligent automation and AI agent interactions. | ||
|
|
||
| **Use Cases** | ||
|
|
||
| - **AI Integration:** Allow AI agents to discover and interact with site capabilities. | ||
| - **Plugin Interoperability:** Enable plugins to discover and use each other's functionality. | ||
| - **Automation Tools:** Provide programmatic access to site features. | ||
| - **API Documentation:** Self-documenting capabilities with schema validation. | ||
| - **Developer Tools:** Standardized way to expose plugin functionality. | ||
|
|
||
| **Registration Example** | ||
|
|
||
| ```php | ||
| add_action( 'abilities_api_init', `my_plugin_register_ability`); | ||
| function my_plugin_register_ability(){ | ||
| wp_register_ability( 'my-plugin/create-post', array( | ||
| 'label' => __( 'Create Post', 'my-plugin' ), | ||
| 'description' => __( 'Creates a new blog post with the provided content', 'my-plugin' ), | ||
| 'input_schema' => array( | ||
| 'type' => 'object', | ||
| 'properties' => array( | ||
| 'title' => array( 'type' => 'string' ), | ||
| 'content' => array( 'type' => 'string' ), | ||
| 'status' => array( 'type' => 'string', 'enum' => array( 'draft', 'publish' ) ) | ||
| ), | ||
| 'required' => array( 'title', 'content' ) | ||
| ), | ||
| 'output_schema' => array( | ||
| 'type' => 'object', | ||
| 'properties' => array( | ||
| 'post_id' => array( 'type' => 'integer' ), | ||
| 'url' => array( 'type' => 'string' ) | ||
| ) | ||
| ), | ||
| 'execute_callback' => 'my_plugin_create_post', | ||
| 'permission_callback' => function( $input ) { | ||
| return current_user_can( 'publish_posts' ); | ||
| } | ||
| )); | ||
| } | ||
| ``` | ||
|
|
||
| This creates a machine-readable capability that AI systems and automation tools can discover, understand, and execute safely within the bounds of WordPress permissions and validation rules. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,61 @@ | ||
| # 2. Getting Started | ||
|
|
||
| ## Installation Options | ||
|
|
||
| Currently, the Abilities API is available as a WordPress plugin. You can install it by cloning the repository into your `wp-content/plugins` directory. | ||
|
|
||
| ```bash | ||
| cd wp-content/plugins | ||
| git clone https://github.com/WordPress/abilities-api | ||
| ``` | ||
|
|
||
| ## Basic Usage Example | ||
|
|
||
| The below example is for a plugin implementation, but it could also be adapted for a theme's functions.php | ||
|
|
||
| ```php | ||
| <?php | ||
|
|
||
| // 1. Define a callback function for your ability | ||
| function my_plugin_get_site_title( array $input = array() ): string { | ||
| return get_bloginfo( 'name' ); | ||
| } | ||
|
|
||
| // 2. Register the ability when the Abilities API is initialized | ||
| // Using abilities_api_init ensures the API is fully loaded | ||
| add_action( 'abilities_api_init', 'my_plugin_register_abilities' ); | ||
|
|
||
| function my_plugin_register_abilities() { | ||
| wp_register_ability( 'my-plugin/get-site-title', array( | ||
| 'label' => __( 'Get Site Title', 'my-plugin' ), | ||
| 'description' => __( 'Retrieves the title of the current WordPress site.', 'my-plugin' ), | ||
| 'input_schema' => array( | ||
| 'type' => 'object', | ||
| 'properties' => array(), | ||
| 'additionalProperties' => false, | ||
| ), | ||
| 'output_schema' => array( | ||
| 'type' => 'string', | ||
| 'description' => 'The site title.', | ||
| ), | ||
| 'execute_callback' => 'my_plugin_get_site_title', | ||
| 'permission_callback' => '__return_true', // Everyone can access this | ||
| 'meta' => array( | ||
| 'category' => 'site-info', | ||
| ), | ||
| ) ); | ||
| } | ||
|
|
||
| // 3. Later, you can retrieve and execute the ability | ||
| add_action( 'admin_init', 'my_plugin_use_ability' ); | ||
|
|
||
| function my_plugin_use_ability() { | ||
| $ability = wp_get_ability( 'my-plugin/get-site-title' ); | ||
|
|
||
| if ( $ability && $ability->has_permission() ) { | ||
| $site_title = $ability->execute(); | ||
| // $site_title now holds the result of get_bloginfo('name') | ||
| // error_log( 'Site Title: ' . $site_title ); | ||
| } | ||
| } | ||
| ``` | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,256 @@ | ||
| ### 3. Registering Abilities (`wp_register_ability`) | ||
|
|
||
| The primary way to add functionality to the Abilities API is by using the `wp_register_ability()` function, typically hooked into the `abilities_api_init` action. | ||
|
|
||
| **Function Signature** | ||
|
|
||
| ```php | ||
| wp_register_ability( string $id, array $args ): ?\WP_Ability | ||
| ``` | ||
|
|
||
| - `$id` (`string`): A unique identifier for the ability. | ||
| - `$args` (`array`): An array of arguments defining the ability configuration. | ||
| - **Return:** (`?\WP_Ability`) An instance of the registered ability if the it was successfully registered, `null` on failure (e.g., invalid arguments, duplicate ID). | ||
gziolo marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| **Parameters Explained** | ||
|
|
||
| The `$args` array accepts the following keys: | ||
|
|
||
| - `label` (`string`, **Required**): A human-readable name for the ability. Used for display purposes. Should be translatable. | ||
| - `description` (`string`, **Required**): A detailed description of what the ability does, its purpose, and its parameters or return values. This is crucial for AI agents to understand how and when to use the ability. Should be translatable. | ||
| - `input_schema` (`array`, **Required**): A JSON Schema definition describing the expected input parameters for the ability's execute callback. Used for validation and documentation. | ||
| - `output_schema` (`array`, **Required**): A JSON Schema definition describing the expected format of the data returned by the ability. Used for validation and documentation. | ||
| - `execute_callback` (`callable`, **Required**): The PHP function or method to execute when this ability is called. | ||
| - The callback receives one argument: an associative array of validated input parameters. | ||
| - The callback should return the result of the ability's operation or return a `WP_Error` object on failure. | ||
| - `permission_callback` (`callable`|`null`, **Optional**): A callback function to check if the current user has permission to execute this ability. | ||
| - Should return `true` if the user has permission, or a `WP_Error` object otherwise. | ||
| - If not provided, the ability will only validate input parameters before execution. | ||
| - This defaults to `true` if not set. | ||
| - `meta` (`array`, **Optional**): An associative array for storing arbitrary additional metadata about the ability. | ||
|
|
||
| **Ability ID Convention** | ||
|
|
||
| The `$id` parameter must follow the pattern `namespace/ability-name`: | ||
| - **Format:** Must contain only lowercase alphanumeric characters (`a-z`, `0-9`), hyphens (`-`), and one forward slash (`/`) for namespacing. | ||
| - **Convention:** Use your plugin slug as the namespace, like `my-plugin/ability-name`. | ||
| - **Examples:** `my-plugin/update-settings`, `woocommerce/get-product`, `contact-form/send-message`, `analytics/track-event` | ||
|
|
||
| **Code Examples** | ||
|
|
||
| **1. Registering a Simple Data Retrieval Ability** | ||
|
|
||
| ```php | ||
| add_action( 'abilities_api_init', 'my_plugin_register_site_info_ability' ); | ||
| function my_plugin_register_site_info_ability() { | ||
| wp_register_ability( 'my-plugin/get-site-info', array( | ||
| 'label' => __( 'Get Site Information', 'my-plugin' ), | ||
| 'description' => __( 'Retrieves basic information about the WordPress site including name, description, and URL.', 'my-plugin' ), | ||
| 'input_schema' => array( | ||
| 'type' => 'object', | ||
| 'properties' => array(), | ||
| 'additionalProperties' => false | ||
| ), | ||
| 'output_schema' => array( | ||
| 'type' => 'object', | ||
| 'properties' => array( | ||
| 'name' => array( | ||
| 'type' => 'string', | ||
| 'description' => 'Site name' | ||
| ), | ||
| 'description' => array( | ||
| 'type' => 'string', | ||
| 'description' => 'Site tagline' | ||
| ), | ||
| 'url' => array( | ||
| 'type' => 'string', | ||
| 'format' => 'uri', | ||
| 'description' => 'Site URL' | ||
| ) | ||
| ) | ||
| ), | ||
| 'execute_callback' => function( $input ) { | ||
| return array( | ||
| 'name' => get_bloginfo( 'name' ), | ||
| 'description' => get_bloginfo( 'description' ), | ||
| 'url' => home_url() | ||
| ); | ||
| }, | ||
| 'permission_callback' => '__return_true' | ||
| )); | ||
| } | ||
| ``` | ||
|
|
||
| **2. Registering an Ability with Input Parameters** | ||
|
|
||
| ```php | ||
| add_action( 'abilities_api_init', 'my_plugin_register_update_option_ability' ); | ||
| function my_plugin_register_update_option_ability() { | ||
| wp_register_ability( 'my-plugin/update-option', array( | ||
| 'label' => __( 'Update WordPress Option', 'my-plugin' ), | ||
| 'description' => __( 'Updates the value of a WordPress option in the database. Requires manage_options capability.', 'my-plugin' ), | ||
| 'input_schema' => array( | ||
| 'type' => 'object', | ||
| 'properties' => array( | ||
| 'option_name' => array( | ||
| 'type' => 'string', | ||
| 'description' => 'The name of the option to update', | ||
| 'minLength' => 1 | ||
| ), | ||
| 'option_value' => array( | ||
| 'description' => 'The new value for the option' | ||
| ) | ||
| ), | ||
| 'required' => array( 'option_name', 'option_value' ), | ||
| 'additionalProperties' => false | ||
| ), | ||
| 'output_schema' => array( | ||
| 'type' => 'object', | ||
| 'properties' => array( | ||
| 'success' => array( | ||
| 'type' => 'boolean', | ||
| 'description' => 'Whether the option was successfully updated' | ||
| ), | ||
| 'previous_value' => array( | ||
| 'description' => 'The previous value of the option' | ||
| ) | ||
| ) | ||
| ), | ||
| 'execute_callback' => function( $input ) { | ||
| $option_name = $input['option_name']; | ||
| $new_value = $input['option_value']; | ||
|
|
||
| $previous_value = get_option( $option_name ); | ||
| $success = update_option( $option_name, $new_value ); | ||
|
|
||
| return array( | ||
| 'success' => $success, | ||
| 'previous_value' => $previous_value | ||
| ); | ||
| }, | ||
| 'permission_callback' => function() { | ||
| return current_user_can( 'manage_options' ); | ||
| } | ||
| )); | ||
| } | ||
| ``` | ||
|
|
||
| **3. Registering an Ability with Plugin Dependencies** | ||
|
|
||
| ```php | ||
| add_action( 'abilities_api_init', 'my_plugin_register_woo_stats_ability' ); | ||
| function my_plugin_register_woo_stats_ability() { | ||
| // Only register if WooCommerce is active | ||
| if ( ! class_exists( 'WooCommerce' ) ) { | ||
| return; | ||
| } | ||
|
|
||
| wp_register_ability( 'my-plugin/get-woo-stats', array( | ||
| 'label' => __( 'Get WooCommerce Statistics', 'my-plugin' ), | ||
| 'description' => __( 'Retrieves basic WooCommerce store statistics including total orders and revenue.', 'my-plugin' ), | ||
| 'input_schema' => array( | ||
| 'type' => 'object', | ||
| 'properties' => array( | ||
| 'period' => array( | ||
| 'type' => 'string', | ||
| 'enum' => array( 'today', 'week', 'month', 'year' ), | ||
| 'default' => 'month', | ||
| 'description' => 'Time period for statistics' | ||
| ) | ||
| ), | ||
| 'additionalProperties' => false | ||
| ), | ||
| 'output_schema' => array( | ||
| 'type' => 'object', | ||
| 'properties' => array( | ||
| 'total_orders' => array( | ||
| 'type' => 'integer', | ||
| 'description' => 'Number of orders in period' | ||
| ), | ||
| 'total_revenue' => array( | ||
| 'type' => 'number', | ||
| 'description' => 'Total revenue in period' | ||
| ) | ||
| ) | ||
| ), | ||
| 'execute_callback' => function( $input ) { | ||
| $period = $input['period'] ?? 'month'; | ||
|
|
||
| // Implementation would calculate stats based on period | ||
| return array( | ||
| 'total_orders' => 42, | ||
| 'total_revenue' => 1250.50 | ||
| ); | ||
| }, | ||
| 'permission_callback' => function() { | ||
| return current_user_can( 'manage_woocommerce' ); | ||
| }, | ||
| 'meta' => array( | ||
| 'requires_plugin' => 'woocommerce', | ||
| 'category' => 'ecommerce' | ||
| ) | ||
| )); | ||
| } | ||
| ``` | ||
|
|
||
| **4. Registering an Ability That May Fail** | ||
|
|
||
| ```php | ||
| add_action( 'abilities_api_init', 'my_plugin_register_send_email_ability' ); | ||
| function my_plugin_register_send_email_ability() { | ||
| wp_register_ability( 'my-plugin/send-email', array( | ||
| 'label' => __( 'Send Email', 'my-plugin' ), | ||
| 'description' => __( 'Sends an email to the specified recipient using WordPress mail functions.', 'my-plugin' ), | ||
| 'input_schema' => array( | ||
| 'type' => 'object', | ||
| 'properties' => array( | ||
| 'to' => array( | ||
| 'type' => 'string', | ||
| 'format' => 'email', | ||
| 'description' => 'Recipient email address' | ||
| ), | ||
| 'subject' => array( | ||
| 'type' => 'string', | ||
| 'minLength' => 1, | ||
| 'description' => 'Email subject' | ||
| ), | ||
| 'message' => array( | ||
| 'type' => 'string', | ||
| 'minLength' => 1, | ||
| 'description' => 'Email message body' | ||
| ) | ||
| ), | ||
| 'required' => array( 'to', 'subject', 'message' ), | ||
| 'additionalProperties' => false | ||
| ), | ||
| 'output_schema' => array( | ||
| 'type' => 'object', | ||
| 'properties' => array( | ||
| 'sent' => array( | ||
| 'type' => 'boolean', | ||
| 'description' => 'Whether the email was successfully sent' | ||
| ) | ||
| ) | ||
| ), | ||
| 'execute_callback' => function( $input ) { | ||
| $sent = wp_mail( | ||
| $input['to'], | ||
| $input['subject'], | ||
| $input['message'] | ||
| ); | ||
|
|
||
| if ( ! $sent ) { | ||
| return new \WP_Error( | ||
| 'email_send_failed', | ||
| sprintf( __( 'Failed to send email' ), 'my-plugin' ) | ||
| ); | ||
| } | ||
|
|
||
| return array( 'sent' => true ); | ||
| }, | ||
| 'permission_callback' => function() { | ||
| return current_user_can( 'publish_posts' ); | ||
| } | ||
| )); | ||
| } | ||
| ``` | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.