---
title: "The WordPress Abilities API, explained"
description: "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."
pubDate: 2026-08-12
source: https://adminkeep.com/blog/wordpress-abilities-api-explained/
---

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:

```php
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](/guides/duplicate-page-wordpress/), [editing a published page without unpublishing
it](/guides/edit-published-page-without-unpublishing/), [reordering
posts](/guides/reorder-wordpress-posts/), and [replacing an image while keeping the same
URL](/guides/replace-image-wordpress-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](/docs/) 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.