Today wp_get_abilities() returns every registered ability, even the ones the current user can't run. AI agents and other clients see tools they will never be allowed to use. The only way to know is to run each permission_callback, and that is not practical when listing abilities.
We looked at answering "can the current user use this ability?" through WP_Ability::check_permissions(). It doesn't fit well, because check_permissions() is bound to the input. It passes the real input to the callback, so the callback can answer questions like "can this user edit this post?". A listing mode with no input would need a new flag. Every callback would then need a third answer ("maybe"), which it can't express today. We can't use null as that signal either, because null is already valid input for abilities without an input schema.
Proposal
Let abilities declare the capabilities a user needs to run them. This builds on an idea from @ovidiu-galatan in WordPress/abilities-api#62: a simple capability-based pre-filter. Abilities the user can't access are filtered out before they are shown to AI agents, and their permission callbacks don't need to run.
<?php
wp_register_ability(
'core/get-site-info',
array(
'label' => __( 'Get Site Information' ),
'description' => __( 'Returns basic information about the site.' ),
'category' => 'site',
'required_capabilities' => array( 'manage_options' ),
'execute_callback' => 'wp_abilities_get_site_info',
)
);
Registration
required_capabilities is a top-level argument, like permission_callback. Core enforces it, so it doesn't belong in meta, which holds hints.
It must be a list of non-empty strings.
When required_capabilities is set, permission_callback becomes optional. Many abilities only check one capability, so they won't need a closure anymore.
An empty list doesn't replace permission_callback. Public abilities still need 'permission_callback' => '__return_true', so "open to everyone" stays an explicit choice.
Execution
check_permissions( $input ) works in this order:
If required_capabilities is set, each capability is checked with current_user_can(). If one is missing, the check fails right away.
If all capabilities pass and there is a permission_callback, it runs with $input, as it does today.
The wp_ability_permission_result filter runs on the final result, including a failure from step 1. This keeps filters like temporary permission elevation working.
So required_capabilities is the first check, and permission_callback adds rules that depend on the input.
Listing
wp_get_abilities() gets a new argument, for example 'current_user_can' => true. It leaves out abilities that declare capabilities the current user doesn't have.
Abilities without required_capabilities stay in the result. Their permission may depend on the input, so they are not hidden.
It runs with the other declarative filters (category, namespace, meta), before item_include_callback.
The REST list route (wp-abilities/v1/abilities) gets a matching query parameter.
The REST response includes required_capabilities, so JavaScript clients can hide UI for abilities the user can't run.
This answers "can the current user use this ability?" when listing, without calling any permission callbacks. Listing, execution, and the client all read the same list, so they can't get out of sync.
Scope
This doesn't cover every case. Abilities whose permission depends on the input will still be listed, and the final check still happens at execution. But it closes an obvious gap: today we list all abilities, even the ones the current user can't run.
Core abilities whose permission check only uses capabilities can move to required_capabilities as part of this change.
Related
PR #13356 explores an eligibility_callback for situational relevance.
WordPress/abilities-api#62
Javascript required
| # | Наименование новости | Тональность | Информативность | Дата публикации |
|---|---|---|---|---|
| 1 | Ticket #66268 (Add `core/content-query` ability) closed | 0 | 9.11 | 16-10-2026 |
| 2 | Changeset [64388]: Abilities API: Add the `core/content-query` ability. Adds a read-only ... | 0 | 7.91 | 16-10-2026 |
| 3 | Ticket #62762 (WP_Widget_Categories::widget(): use int or bool for $cat_args values ...) closed | 0 | 6.91 | 16-10-2026 |
| 4 | Ticket #66328 (Recovery mode does not recognize network-activated plugins) created | 0 | 5.92 | 16-10-2026 |
| 5 | Changeset [64413]: Widgets: Use booleans in Categories widget arguments. The Categories ... | 0 | 7.44 | 16-10-2026 |
| 6 | Функция WordPress: wp_register_ability_category() | 0 | 10 | 19-07-2026 |
| 7 | Ticket #66246 (Interactivity API: Add server-side processing for the new data-wp-html ...) reopened | 0 | 9.54 | 16-10-2026 |
| 8 | Функция WordPress: wp_get_ability() | 0 | 19.09 | 19-07-2026 |
| 9 | Lisboa WordPress Meetup: WordPress Abilities API e como interagir com o WooCommerce em linguagem humana | 0 | 9.64 | 09-06-2026 |