releases.sh

Ability queries gain category, namespace, and meta filters

v7.1

1 feature1 enhancementThis release1 featureNew capabilities1 enhancementImprovements to existing featuresAI-tallied from the release notes
From the original release noteView original ↗

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_include
  • wp_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.

  1. Why was this change needed?
  2. Filtering by category
  3. Filtering by namespace
  4. Filtering by metadata
  5. Combining declarative filters
  6. Custom per-item filtering
  7. Processing the complete result
  8. New global filters
    1. wp_get_abilities_item_include
    2. wp_get_abilities_result
  9. Filtering order
  10. Discovery over REST
    1. Built-in annotation types
  11. Backward compatibility
  12. Retrieving the raw registry
  13. Filtering does not replace authorisation
  14. 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-export category.
  • Use the my-plugin namespace.
  • Have resolved public metadata set to true.

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 true to include the ability.
  • Return false to 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 to wp_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 to wp_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:

  1. Match the category argument.
  2. Match the namespace argument.
  3. Match the meta argument.
  4. Run item_include_callback.
  5. Apply wp_get_abilities_item_include.
  6. Add included abilities to the matched result.
  7. Run result_callback on the complete result.
  8. 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_include
  • wp_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.

#abilities-api, #7-1, #dev-notes, #dev-notes-7-1, #rest-api

Fetched August 5, 2026