Ability queries gain category, namespace, and meta filters
v7.1
WordPress 7.1 extends <a href="https://developer.wordpress.org/reference/functions/wp_get_abilities/">wp_get_abilities()</a> with a standard way to filter registered abilities.
The function now accepts an optional $args array that can filter abilities by category, namespace, or metadata. It also supports callbacks for custom per-item filtering and final result processing.
Two new WordPress filters allow plugins to influence ability retrieval across the site:
wp_get_abilities_item_includewp_get_abilities_result
The REST API’s abilities list controller now uses wp_get_abilities() instead of implementing category filtering separately. It also supports filtering abilities by namespace.
- Why was this change needed?
- Filtering by category
- Filtering by namespace
- Filtering by metadata
- Combining declarative filters
- Custom per-item filtering
- Processing the complete result
- New global filters
- Filtering order
- Discovery over REST
- Backward compatibility
- Retrieving the raw registry
- Filtering does not replace authorisation
- When to use each option
Why was this change needed?
Before WordPress 7.1, there were two ways to retrieve abilities:
// Retrieve every registered ability.
$abilities = wp_get_abilities();
// Retrieve one named ability.
$ability = wp_get_ability( 'my-plugin/export-users' );
wp_get_abilities() always returned the complete registry. A caller needing a subset had to retrieve every ability and filter the result manually:
$abilities = array_filter(
wp_get_abilities(),
function ( WP_Ability $ability ): bool {
return 'data-export' === $ability->get_category();
}
);
Several consumers developed their own versions of this pattern for category, namespace, and metadata checks. The REST abilities controller also performed its own category filtering after retrieving the complete registry.
This led to:
- Duplicated filtering code.
- Inconsistent filtering semantics between consumers.
- Different behaviour between the PHP and REST APIs.
- No standard extension points for ability selection.
- Additional filtering passes over the registry.
WordPress 7.1 moves this work into wp_get_abilities(), providing one shared filtering pipeline for Core and plugins.
Filtering by category
Pass a category slug using the category argument:
$abilities = wp_get_abilities(
array(
'category' => 'data-export',
)
);
The comparison is exact. Only abilities whose category exactly matches the supplied string are returned.
The category must be passed as a single string. Arrays of category slugs are not supported.
Filtering by namespace
Use namespace to retrieve abilities registered under a particular namespace:
$abilities = wp_get_abilities(
array(
'namespace' => 'my-plugin',
)
);
The namespace is passed without the trailing slash. Both of the following values are normalised to the same namespace:
'namespace' => 'my-plugin',
'namespace' => 'my-plugin/',
An ability such as my-plugin/export-users matches, while another-plugin/export-users does not.
Namespace matching includes the namespace delimiter. Passing my-plugin does not accidentally match an ability registered under a similarly named my-plugin-extra namespace.
Filtering by metadata
The meta argument selects abilities whose metadata contains the specified key-value pairs:
$abilities = wp_get_abilities(
array(
'meta' => array(
'public' => true,
),
)
);
All supplied metadata conditions must match:
$abilities = wp_get_abilities(
array(
'meta' => array(
'public' => true,
'show_in_rest' => true,
),
)
);
Nested metadata is supported:
$abilities = wp_get_abilities(
array(
'meta' => array(
'my_client' => array(
'public' => true,
),
),
)
);
Metadata comparisons are strict. The value true does not match 1, and false does not match 0.
The metadata filter checks that every requested condition exists and matches. An ability may contain additional metadata that was not included in the query.
Combining declarative filters
The category, namespace, and meta arguments can be combined:
$abilities = wp_get_abilities(
array(
'category' => 'data-export',
'namespace' => 'my-plugin',
'meta' => array(
'public' => true,
),
)
);
Conditions are combined using AND logic. An ability must satisfy every supplied argument to be included.
In this example, the result contains only abilities that:
- Belong to the
data-exportcategory. - Use the
my-pluginnamespace. - Have resolved
publicmetadata set totrue.
Custom per-item filtering
Conditions that cannot be expressed using the declarative arguments can be handled with item_include_callback:
$abilities = wp_get_abilities(
array(
'namespace' => 'my-plugin',
'item_include_callback' => function (
WP_Ability $ability
): bool {
return my_plugin_should_include_ability( $ability );
},
)
);
The callback runs once for every ability that passed the declarative filters. It receives the WP_Ability instance and must return a boolean:
- Return
trueto include the ability. - Return
falseto exclude it.
This callback is scoped to the current wp_get_abilities() call. It does not affect ability retrieval elsewhere.
Use it for conditions such as:
- Custom metadata relationships.
- Context-dependent visibility.
- Integration-specific rules.
- Conditions involving more than one ability property.
Processing the complete result
Use result_callback when an operation requires the complete matched array:
$abilities = wp_get_abilities(
array(
'namespace' => 'my-plugin',
'result_callback' => function ( array $abilities ): array {
uasort(
$abilities,
function (
WP_Ability $first,
WP_Ability $second
): int {
return strcasecmp(
$first->get_label(),
$second->get_label()
);
}
);
return array_slice(
$abilities,
0,
10,
true
);
},
)
);
The result callback runs after all per-item matching has completed. It is suitable for:
- Sorting.
- Slicing or pagination.
- Reordering.
- Other final result transformations.
Like item_include_callback, result_callback applies only to the current function call.
Registered abilities are normally returned in an associative array keyed by ability name. When sorting or slicing the result, preserve those keys when downstream code depends on them.
New global filters
WordPress 7.1 also introduces two filters for plugins that need to affect ability retrieval beyond a single call site.
wp_get_abilities_item_include
The wp_get_abilities_item_include filter runs for every ability that passed the declarative conditions and the caller’s item_include_callback:
add_filter(
'wp_get_abilities_item_include',
function (
bool $include,
WP_Ability $ability,
array $args
): bool {
if ( 'my-plugin/private-operation' === $ability->get_name() ) {
return false;
}
return $include;
},
10,
3
);
The filter receives:
$include: Whether the ability should currently be included.$ability: The ability being evaluated.$args: The complete arguments passed towp_get_abilities().
Because declarative mismatches are removed before this filter runs, the filter cannot add an ability that failed category, namespace, or meta matching. It can influence the inclusion of abilities that have reached this stage, as well as their global exclusion.
wp_get_abilities_result
The wp_get_abilities_result filter receives the complete result after the caller’s result_callback:
add_filter(
'wp_get_abilities_result',
function ( array $abilities, array $args ): array {
// Apply site-wide result processing when appropriate.
return $abilities;
},
10,
2
);
The filter receives:
$abilities: The final matched array.$args: The complete arguments passed towp_get_abilities().
It can be used for site-wide sorting, reordering, or other final processing.
These are global filters. Plugins should use them only when the behaviour is intended to affect every relevant caller. For logic that belongs to one operation, prefer item_include_callback or result_callback.
Filtering order
The complete pipeline runs in the following order:
- Match the
categoryargument. - Match the
namespaceargument. - Match the
metaargument. - Run
item_include_callback. - Apply
wp_get_abilities_item_include. - Add included abilities to the matched result.
- Run
result_callbackon the complete result. - Apply
wp_get_abilities_result.
The declarative checks, item callback, and item filter run within a single pass over the registry.
This avoids the separate array_filter() passes that consumers previously had to implement.
Discovery over REST
The REST collection endpoint delegates to wp_get_abilities() and exposes the declarative filters as query parameters:
GET /wp-json/wp-abilities/v1/abilities?namespace=my-plugin
GET /wp-json/wp-abilities/v1/abilities?category=my-plugin-content
GET /wp-json/wp-abilities/v1/abilities?meta[annotations][readonly]=true
Parameters can be combined and use the same AND logic.
?category=data-export&namespace=my-plugin
Every collection request also forces meta.show_in_rest = true internally. Supplying another metadata query can’t reveal an ability that is hidden from REST. The endpoint still requires an authenticated WordPress user, and executing a listed ability still requires its permission callback to pass.
Custom metadata needs a REST parameter schema if its query-string values should be coerced before strict comparison. Without one, 'true' will never match boolean true. The rest_abilities_collection_params filter extends the collection argument schema:
add_filter(
'rest_abilities_collection_params',
static function ( array $params ): array {
$params['meta']['properties']['my_plugin'] = array(
'type' => 'object',
'properties' => array(
'enabled' => array(
'type' => 'boolean',
),
),
);
return $params;
}
);
After that declaration, the REST API casts "true" to a boolean true before the value reaches the metadata-matching logic.
GET /wp-json/wp-abilities/v1/abilities?meta[my_plugin][enabled]=true
Built-in annotation types
The known readonly, destructive, and idempotent annotation values are coerced from query strings to boolean values before strict matching. Core declares the schema for these standard ability annotations.
Each accepts a boolean or null, so REST can correctly cast their query values without a plugin extending the schema:
?meta[annotations][readonly]=true
Use rest_abilities_collection_params filter when making additional metadata fields queryable, especially boolean, integer, number, array, or object values that cannot be matched correctly as untyped query strings.
Backward compatibility
The $args parameter is optional:
$abilities = wp_get_abilities();
Existing calls remain valid, and the function still returns an array of WP_Ability instances keyed by ability name.
Code that manually filters the result can continue to work:
$abilities = array_filter(
wp_get_abilities(),
'my_plugin_filter_abilities'
);
However, plugins should migrate common category, namespace, and metadata checks to the new arguments. Doing so reduces duplicated code and allows Core and other integrations to use consistent matching behaviour.
One behavioural detail deserves particular attention: the two new global filters run even when wp_get_abilities() is called without arguments.
As a result, the following call now means “retrieve abilities through the standard filtering pipeline”:
$abilities = wp_get_abilities();
It does not necessarily mean “retrieve raw registry contents,” because another plugin can alter the result through wp_get_abilities_item_include and wp_get_abilities_result.
Retrieving the raw registry
Code that specifically needs the complete, unfiltered registry can use WP_Abilities_Registry::get_all_registered():
$registry = WP_Abilities_Registry::get_instance();
$abilities = $registry->get_all_registered();
This bypasses:
- Declarative filtering.
- Caller callbacks.
wp_get_abilities_item_includewp_get_abilities_result
Most application and integration code should continue using wp_get_abilities(). Direct registry access is appropriate only when raw registered state is explicitly required, such as low-level debugging or registry inspection.
Filtering does not replace authorisation
Filtering controls which abilities are returned during discovery. It does not determine whether the current user may execute an ability.
An ability’s permission_callback remains responsible for authorisation:
'permission_callback' => function (): bool {
return current_user_can( 'manage_options' );
},
Developers should not assume that an ability returned by wp_get_abilities() is executable by the current user.
Similarly, excluding an ability from a filtered result is not a security boundary. Any sensitive operation must enforce its permissions when the ability is executed.
When to use each option
Use declarative arguments for standard selection:
wp_get_abilities(
array(
'category' => 'data-export',
'namespace' => 'my-plugin',
'meta' => array(
'public' => true,
),
)
);
Use item_include_callback for custom conditions that apply to one call:
wp_get_abilities(
array(
'item_include_callback' => 'my_plugin_should_include_ability',
)
);
Use result_callback for call-specific sorting or slicing:
wp_get_abilities(
array(
'result_callback' => 'my_plugin_prepare_ability_results',
)
);
Use wp_get_abilities_item_include or wp_get_abilities_result only for behaviour intended to affect ability retrieval across callers.
Use WP_Abilities_Registry::get_all_registered() only when code explicitly requires raw, unfiltered registry data.
Together, these changes make wp_get_abilities() the shared discovery and filtering primitive for the Abilities API, replacing duplicated filtering implementations with a consistent, extensible pipeline.
These changes were introduced in changeset [62420] for Trac ticket #64990.
Props to @benjamin_zekavica for peer review, and @gziolo for review, technical guidance, and suggested improvements.
Fetched August 5, 2026

