Skip to content

Abilities API: Refine filtering and expose meta over REST#12233

Open
gziolo wants to merge 1 commit into
WordPress:trunkfrom
gziolo:fix/64990-abilities-filtering-feedback
Open

Abilities API: Refine filtering and expose meta over REST#12233
gziolo wants to merge 1 commit into
WordPress:trunkfrom
gziolo:fix/64990-abilities-filtering-feedback

Conversation

@gziolo

@gziolo gziolo commented Jun 19, 2026

Copy link
Copy Markdown
Member

Trac ticket: https://core.trac.wordpress.org/ticket/64990

Summary

This addresses the remaining feedback on filtering support for wp_get_abilities(). It makes the declarative filter arguments consistent and exposes meta filtering through the REST API.

Details

Single-string category. The category argument now takes one slug, the same shape as namespace. Both do an exact match on a single value. A caller that needs to match several values can use item_include_callback. Starting with a single string keeps the public surface simple and predictable, and it leaves room to accept arrays later without a breaking change. The reverse (arrays now, single value later) could not be undone.

REST meta parameter. The abilities list endpoint (/wp-abilities/v1/abilities) now accepts a meta query parameter, next to category and namespace. Conditions combine with AND logic and may be nested. The schema declares the well-defined behavioral annotations (readonly, destructive, idempotent), so a query-string value such as true is coerced to a real boolean before matching. Open-ended meta keys still pass through as sent.

Visibility stays protected. The endpoint always forces meta[show_in_rest] => true. Caller meta is merged first, so this forced condition always wins. A caller cannot use meta to reveal abilities that are hidden from REST.

Testing

New and updated unit tests cover:

  • category as a single string, and that a non-string value is ignored.
  • The REST meta parameter, including string-to-boolean coercion for annotations.
  • AND logic across several meta conditions.
  • The show_in_rest guard, so meta cannot widen visibility.
npm run test:php -- --group abilities-api

Note

The tests/qunit/fixtures/wp-api-generated.js change is the auto-generated REST API client fixture, regenerated to add the new meta query parameter.

🤖 Generated with Claude Code

Narrow the `category` argument of `wp_get_abilities()` to a single string so it
matches `namespace`. Both arguments now take one slug. Callers that need to
match several values can use `item_include_callback`. Starting simple keeps the
public surface predictable and leaves room to accept arrays later without a
breaking change.

Add a `meta` query parameter to the REST abilities list endpoint, alongside
`category` and `namespace`. The schema declares the well-defined behavioral
annotations (`readonly`, `destructive`, `idempotent`), so a query-string value
such as "true" is coerced to a boolean before matching. The forced
`show_in_rest` condition always wins, so a caller cannot use meta to reveal
abilities that are hidden from REST.

Expand unit test coverage for the single-string category, the REST meta
parameter, AND logic across several conditions, and the `show_in_rest` guard.
Regenerate the REST API client fixture for the new query parameter.

Fixes #64990.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jun 19, 2026

Copy link
Copy Markdown

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

Core Committers: Use this line as a base for the props when committing in SVN:

Props gziolo, jorgefilipecosta.

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@github-actions

Copy link
Copy Markdown

Hi there! 👋

Thank you for your contribution to WordPress! 💖

It looks like this is your first pull request to wordpress-develop. Here are a few things to be aware of that may help you out!

No one monitors this repository for new pull requests. Pull requests must be attached to a Trac ticket to be considered for inclusion in WordPress Core. To attach a pull request to a Trac ticket, please include the ticket's full URL in your pull request description.

Pull requests are never merged on GitHub. The WordPress codebase continues to be managed through the SVN repository that this GitHub repository mirrors. Please feel free to open pull requests to work on any contribution you are making.

More information about how GitHub pull requests can be used to contribute to WordPress can be found in the Core Handbook.

Please include automated tests. Including tests in your pull request is one way to help your patch be considered faster. To learn about WordPress' test suites, visit the Automated Testing page in the handbook.

If you have not had a chance, please review the Contribute with Code page in the WordPress Core Handbook.

The Developer Hub also documents the various coding standards that are followed:

Thank you,
The WordPress Project

@gziolo gziolo self-assigned this Jun 19, 2026
@gziolo gziolo requested a review from jorgefilipecosta June 19, 2026 11:14
@github-actions

Copy link
Copy Markdown

Test using WordPress Playground

The changes in this pull request can previewed and tested using a WordPress Playground instance.

WordPress Playground is an experimental project that creates a full WordPress instance entirely within the browser.

Some things to be aware of

  • All changes will be lost when closing a tab with a Playground instance.
  • All changes will be lost when refreshing the page.
  • A fresh instance is created each time the link below is clicked.
  • Every time this pull request is updated, a new ZIP file containing all changes is created. If changes are not reflected in the Playground instance,
    it's possible that the most recent build failed, or has not completed. Check the list of workflow runs to be sure.

For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation.

Test this pull request with WordPress Playground.

@jorgefilipecosta jorgefilipecosta left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we may have an issue. On ticket https://core.trac.wordpress.org/ticket/64990 we refer the case of filtering mcp.public === true.
But currently these filters don't work because mcp is an additional property, REST leaves the query-string value as 'true', while _wp_get_abilities_match_meta() strictly compares it with boolean true, so the ability is excluded.

I had this test case a reproduction of the bug:

<?php

class Tests_PR_12233_Custom_Meta extends WP_UnitTestCase {
	protected $server;

	public function set_up(): void {
		parent::set_up();

		global $wp_current_filter, $wp_rest_server;

		$wp_current_filter[] = 'wp_abilities_api_categories_init';
		wp_register_ability_category(
			'probe',
			array(
				'label'       => 'Probe',
				'description' => 'Probe category.',
			)
		);
		array_pop( $wp_current_filter );

		$wp_current_filter[] = 'wp_abilities_api_init';
		wp_register_ability(
			'probe/custom-meta',
			array(
				'label'               => 'Custom meta',
				'description'         => 'Custom boolean meta.',
				'category'            => 'probe',
				'execute_callback'    => '__return_true',
				'permission_callback' => '__return_true',
				'meta'                => array(
					'show_in_rest' => true,
					'mcp'          => array( 'public' => true ),
				),
			)
		);
		array_pop( $wp_current_filter );

		$wp_rest_server = new WP_REST_Server();
		$this->server   = $wp_rest_server;
		do_action( 'rest_api_init' );
		wp_set_current_user( self::factory()->user->create( array( 'role' => 'subscriber' ) ) );
	}

	public function tear_down(): void {
		global $wp_rest_server;

		wp_unregister_ability( 'probe/custom-meta' );
		wp_unregister_ability_category( 'probe' );
		$wp_rest_server = null;

		parent::tear_down();
	}

	public function test_query_string_boolean_for_custom_meta_matches(): void {
		$request = new WP_REST_Request( 'GET', '/wp-abilities/v1/abilities' );
		$request->set_query_params(
			array(
				'meta' => array(
					'mcp' => array( 'public' => 'true' ),
				),
			)
		);

		$response = $this->server->dispatch( $request );
		$names    = wp_list_pluck( $response->get_data(), 'name' );

		$this->assertContains( 'probe/custom-meta', $names );
	}
}

The solution for this is not clear I guess the possibility is to scope the REST parameter to the explicitly declared annotation fields. But that removes filtering options. Or we extend the API so when someone uses a an annotation they can pass additional schema information.

'properties' => array(
'annotations' => array(
'description' => __( 'Annotations for the ability.' ),
'type' => array( 'boolean', 'null' ),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we update the type here to match the type we are using on the new added code on get_collection_params?

@jorgefilipecosta

Copy link
Copy Markdown
Member

Hi @gziolo the PR seems to be in a good shape, just left some comments for consideration.

}

if ( ! empty( $request['meta'] ) ) {
// Merge caller meta first so the forced show_in_rest filter always wins.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks like a typical AI tutorial style comment, i'd remove it as it just states the obvious

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was deliberately added to ensure it doesn't regress to allow fetching abilities that were explicitly excluded from the REST API. If any, that can be rephrased if you find it unclear.

// Step 1a: Filter by category (OR logic within the arg).
if ( ! empty( $category ) && ! in_array( $ability->get_category(), $category, true ) ) {
// Step 1a: Filter by category.
if ( '' !== $category && $ability->get_category() !== $category ) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

did you verify that the logic remains the same?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As mentioned in the ticket and the description of this PR, this is an anticipated change to align with the current handling of namespace and category through the REST API. I committed the initial implementation during this release cycle, so there’s still room for refinement in the implementation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants