The WordPress Abilities API, explained
WordPress 6.9 gave every site a machine-readable list of the things it can do, and 7.0 gave AI a way to call them. Here is what the Abilities API is, how it works, and what a plugin that respects your settings does with it.
WordPress 6.9 shipped something that never appears on a screen, which is probably why most site owners have not heard of it. It is called the Abilities API, and it is a registry — one list, kept by core, of the discrete things a particular site can actually do.
An ability is a single named action. “Duplicate a post.” “Replace the file behind an attachment.” Each entry carries a name, a human-readable label and description, a typed schema for what you have to send it, a typed schema for what comes back, and — the part that matters most — a permission callback that decides, on every single call, whether the current user is allowed to run it at all.
Core and plugins register into the same list. That list is exposed over the REST API under
the wp-abilities/v1 namespace, where a caller can do three things: ask for the abilities
this site has, run one of them, and ask for the categories they are filed under.
What makes this interesting is what it replaces. “What can this site do?” has never had an answer you could ask for. The knowledge was scattered — a plugin’s features lived in a few ad-hoc REST routes, in admin screens with form handlers and nonces behind them, maybe in a WP-CLI command, maybe in an AJAX action whose name only its author knew. Anything that wanted to drive a WordPress site from outside had to learn each plugin’s private vocabulary, one plugin at a time, and re-learn it whenever the plugin changed. The integrations were bespoke by necessity, and they broke constantly.
The Abilities API replaces all of that with a single vocabulary. A plugin declares its actions once, in a form that requires no prior knowledge to read, and any caller — including callers the plugin’s author never imagined — can discover them, understand what they accept, and invoke them under the site’s own rules.
Why core built it
The honest answer is that the agents arrived and WordPress needed a door for them that wasn’t a scraped admin screen.
Think about what has been trying to operate WordPress sites lately. AI assistants that a site owner asks to tidy up a category. WP-CLI scripts. Automation platforms. Other plugins that want to hand off a job cleanly rather than reach into a competitor’s internals. Every one of them faced the same wall: no way to find out what a site can do without a human reading the plugin’s documentation and writing an integration by hand. The work did not scale, and the alternative — driving the admin UI programmatically, or guessing at REST routes — was fragile and unsafe in equal measure.
WordPress 7.0 closed the loop with the AI Client, which can take the abilities registered on a site and resolve them into functions an AI model is able to call directly. The model is handed a list of what this specific site can do, with the exact shape of the arguments each one wants, and it can pick one and call it.
That sounds alarming until you look at where the decision actually lives. An ability’s
permission_callback runs on every invocation, on the server, in PHP, against the current
user. The model does not get privileges. It gets the ability to ask. WordPress decides — the
same way it decides when the request comes from a browser, because in the end it is the same
capability check either way. A model asking to delete a post on behalf of a subscriber gets
exactly what a subscriber gets: refused.
How it works in sixty seconds
Registration happens on the wp_abilities_api_init action, through
wp_register_ability( string $name, array $args ), which hands back a WP_Ability object (or
null if the registration was rejected). Here is a complete one:
wp_register_ability(
'my-plugin/duplicate-post',
array(
'label' => __( 'Duplicate a post', 'my-plugin' ),
'description' => __( 'Copies a post as a new draft; the original is not modified.', 'my-plugin' ),
'input_schema' => array(
'type' => 'object',
'properties' => array(
'post_id' => array( 'type' => 'integer' ),
),
'required' => array( 'post_id' ),
),
'output_schema' => array(
'type' => 'object',
'properties' => array(
'post_id' => array( 'type' => 'integer' ),
),
),
'permission_callback' => 'my_plugin_can_duplicate',
'execute_callback' => 'my_plugin_duplicate',
'meta' => array(
'annotations' => array(
'readonly' => false,
'destructive' => false,
'idempotent' => false,
),
),
)
);
The name is namespaced, so abilities from different plugins never collide. The label and description are what a human — or a model — reads to decide whether this is the right tool.
The two schemas are the contract. input_schema says what a caller must send and in what
shape, so a caller can construct a valid request without reading your source; output_schema
says what it will get back, so it can use the result without guessing.
permission_callback is consulted per call, not once at registration. execute_callback only
runs if it passed.
The meta.annotations are advisory labels for whoever is calling: readonly says the ability
only looks at things, destructive warns that it removes or overwrites something, and
idempotent says calling it twice is the same as calling it once. An agent uses these to
decide what it can try freely and what deserves a confirmation first. Related abilities can
also be grouped under a category registered with wp_register_ability_category(), so a long
list stays navigable.
Case study: five abilities in Adminkeep
Concrete example, from a plugin that adopted this. Adminkeep registers five abilities under
the wp-pro-admin/ prefix — the plugin’s original name, kept so existing integrations keep
working — and each one belongs to a feature you can already see in the
settings screen: Duplicate owns duplicate-post; Live Draft owns both open-live-draft and
merge-live-draft; Order owns reorder-posts; Replace Media owns replace-media. They are
the same jobs the guides cover for humans — duplicating a
page, editing a published page without unpublishing
it, reordering
posts, and replacing an image while keeping the same
URL. Nothing new was invented for agents. The
existing actions were described.
Three decisions shaped how they were described, and they are the ones worth stealing.
Only enabled features register. A feature you have switched off registers no abilities, in exactly the way it registers no hooks. There is no separate “expose to agents” toggle to forget about, and no ability sitting in the registry for something you disabled two months ago. An agent’s picture of the site is always the picture your settings describe — if it isn’t on, it isn’t discoverable. (The full settings reference covers which features exist and what each one turns on.)
Permission callbacks re-apply the feature’s own settings, not just capabilities. The duplicate ability checks the capability, and then re-checks that the post’s type is one you actually ticked in settings. That second check is the difference between “this user may edit posts” and “this site allows duplicating this kind of thing.” Without it, an agent would quietly get more reach than the button in the admin has. With it, the ability grants exactly what the interface grants and nothing else.
replace-media takes a source attachment, never a file path. This one is the sharpest,
because it is a design decision rather than a check. Abilities are reachable over REST. If the
ability accepted a filesystem path, then anyone holding edit_post on a single attachment
could point it at any file the web server can read and publish the contents — configuration,
credentials, whatever. Taking an attachment ID instead means the input can only name something
already in the media library, which the permission check can reason about. Design the input
for the hostile caller, and the friendly one is covered for free.
Taken together, that is the whole of this plugin’s relationship with AI: it exposes actions that get called. It holds no API key, makes no outbound model requests, and has no opinion about which assistant you use. The intelligence lives on the other end of the line; this side just answers clearly and enforces your rules.
What to ask of your plugins
Adoption is early, and over the next year a lot of plugins will register abilities. That makes this a good moment to know what a careful implementation looks like, because the difference is not visible from the plugin listing.
Three questions are enough:
Does a disabled feature disappear from the ability list? If a plugin registers everything regardless of your settings, then your settings describe the admin screens only, and an agent is working from a different map than you are.
Do the permission checks match what the admin screens allow? Capability checks alone are the floor, not the ceiling. If a feature is restricted to certain post types or certain roles in its own settings, the ability should honor those restrictions too.
Are the inputs designed so a caller can’t reach further than a user could? Anything that accepts a path, a URL, or a raw identifier deserves a second look — the whole point of a typed schema is that it can be narrow.
If a plugin gets all three right, handing an assistant access to your site is no more dangerous than handing it a login with the same role, which is the correct amount of dangerous. Adminkeep’s content tools work this way today — five abilities, all of them off unless you turned the feature on.