Filtering registered abilities with wp_get_abilities() in WordPress 7.1

WordPress 7.1 extends wp_get_abilities() with a standard way to filterFilter Filters are one of the two types of Hooks https://codex.wordpress.org/Plugin_API/Hooks. They provide a way for functions to modify data of other functions. They are the counterpart to Actions. Unlike Actions, filters are meant to work in an isolated manner, and should never have side effects such as affecting global variables and output. 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 APIREST API The REST API is an acronym for the RESTful Application Program Interface (API) that uses HTTP requests to GET, PUT, POST and DELETE data. It is how the front end of an application (think “phone app” or “website”) can communicate with the data store (think “database” or “file system”) https://developer.wordpress.org/rest-api/’s abilities list controller now uses wp_get_abilities() instead of implementing categoryCategory The 'category' taxonomy lets you group posts / content together that share a common bond. Categories are pre-defined and broad ranging. filtering separately. It also supports filtering abilities by namespace.

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 PHPPHP The web scripting language in which WordPress is primarily architected. WordPress requires PHP 7.4 or higher 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 CoreCore Core is the set of software required to run WordPress. The Core Development Team builds WordPress. 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 pluginPlugin A plugin is a piece of software containing a group of functions that can be added to a WordPress website. They can extend functionality or add new features to your WordPress websites. WordPress plugins are written in the PHP programming language and integrate seamlessly with WordPress. These can be free in the WordPress.org Plugin Directory https://wordpress.org/plugins/ or can be cost-based plugin from a third-party. 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 APIAPI An API or Application Programming Interface is a software intermediary that allows programs to interact with each other and share data in limited, clearly defined ways., replacing duplicated filtering implementations with a consistent, extensibleExtensible This is the ability to add additional functionality to the code. Plugins extend the WordPress core software. pipeline.

These changes were introduced in changeset [62420] for TracTrac An open source project by Edgewall Software that serves as a bug tracker and project management tool for WordPress. ticketticket Created for both bug reports and feature development on the bug tracker. #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