Skip to content

Conversation

@jorgefilipecosta
Copy link
Member

@jorgefilipecosta jorgefilipecosta commented Dec 26, 2025

Part of: WordPress/ai#40 cc: @Jameswlepage

Inspired by the work on https://github.com/galatanovidiu/mcp-adapter-implementation-example/tree/experiment/layerd-mcp-tools/includes/Abilities by @galatanovidiu.

Core abilities organization

This PR also proposes a logic for how core abilities are organized. In abilities.php, we have two functions: wp_register_core_abilities and wp_register_ability_category (or in the case of Gutenberg, _gutenberg_register_core_abilities and _gutenberg_register_core_ability_categories). These functions then call ability registration functions that are inside the abilities folder. If the ability is simple, it can be registered in just a single internal function, e.g., _wp_register_site_info_ability; for complex abilities, we can register them in a class (like this post management one).

The abilities can be in both Gutenberg and core. Having them in Gutenberg allows us to use them in the workflows functionality that is being worked on by @senadir and allows us to get some testing before core is released.

Gutenberg unregisters the equivalent core ones, so Gutenberg is the source of truth. The same ability can exist in Gutenberg and core, but Gutenberg takes precedence so we can test changes in Gutenberg before releasing in core (similar to what happens with blocks and other WordPress artifacts).

Core Post management abilities

This PR adds core post management abilities for the WordPress abilities API: core/create-post, core/get-post, core/find-posts, and core/update-post.

It supports nested query support for meta_query, tax_query, and date_query matching WordPress's native WP_Query structure with AND/OR relations. This allows very complex query operations which the REST API does not allow and may be useful for agents to find information.

It uses the permission callback mechanism and tries to correctly check permissions for all cases. The basic permission checking logic first checks the basic and common use case (user can edit, create, or see a post), then we go into specifics in separate functions that are reused for checking status changes (e.g., publish), author changes, and taxonomy assignment permissions.

The class WP_Posts_Abilities_Gutenberg is organized into 6 main areas:

  • Ability Registration: The main part that does the wp_register_ability calls.
  • Initialization: Initializes shared data between the abilities, e.g., schemas.
  • Output Formatting: Formats the post object (and taxonomies) for output shared between all abilities.
  • Permission Checking: Permission checking logic; some parts are also shared between abilities.
  • Query Processing: Utilities to process queries and map the ability input format to the WP_Query format.
  • Data Processing: Utilities to process input data, sanitize it, map to other formats, etc.

Missing

The idea of this PR is mainly to get feedback if this is the right direction for the post management abilities. There are some things that are missing:

  • Automated testing: Deeply covering all the abilities. If we agree with this approach, I will add the tests to this PR.
  • Partial Edits: Not all edits are possible, e.g., it is not possible to mark a post as sticky. We need to decide if marking a post as sticky should be done on the update/create post abilities, following the REST API approach, or if we should have a specific ability for that, following the wp_core PHP API approach where we have the wp_update_post function and also stick_post/unstick_post functions. We can decide on what to do regarding the missing edits as follow-ups, as the PR is already too big.
  • Pagination: Pagination was not yet implemented; we need to have a general solution for pagination at the abilities API level.

Test plan

  • Open /wp-admin/edit.php?post_type=post.
  • Open the browser console and paste the following code to load the abilities API:
const abilitiesAPI = (await import( '@wordpress/abilities' ) );
  • Verify core/create-post creates posts with various fields (title, content, status, meta, taxonomies):
await abilitiesAPI.executeAbility('core/create-post', {post_type: 'post', title: 'Hello World', content: 'My awesome content', 'status': 'publish', meta: {a:23, c:1} } );
  • Verify core/get-post retrieves posts by ID with optional taxonomy/meta inclusion:
await abilitiesAPI.executeAbility('core/get-post', {id: 334, include_meta: true, include_taxonomies: true} )
  • Verify core/find-posts queries work with nested meta_query, tax_query, and date_query.
  • Create posts with the following structure where the title a-23|c-1 represents a post with meta key "a" value of 23 and meta key "c" value of 1. Also adds the correct tags.
Screenshot 2025-12-26 at 17 25 02
  • Execute complex find post queries like:
await abilitiesAPI.executeAbility('core/find-posts', {

    post_type: 'post',

    include_meta: true,

    meta_query: {

        queries: [

            {

                key: 'footnotes',

                compare: 'EXISTS',

            },  

        ]

    }

});

await abilitiesAPI.executeAbility('core/find-posts', {

    post_type: 'post',

    include_meta: true,

    meta_query: {

        relation: 'AND',

        queries: [

            {

                key: 'a',

                compare: '=',

                value: '23'

            },

            {

                relation: 'OR',

                queries: [

            {

                key: 'b',

                compare: '=',

                value: '1'

            }, {

                key: 'c',

                compare: '=',

                value: '1'

            } ] }

                

        ]

    }

});

await abilitiesAPI.executeAbility('core/find-posts', {

    post_type: 'post',

    include_meta: true,

    tax_query: {

        relation: 'AND',

        queries: [

            {

                taxonomy: 'post_tag',

                field: 'slug',

                terms: ['t-23']

            },

            {

                relation: 'OR',

                queries: [

                    {

                        taxonomy: 'post_tag',

                        field: 'slug',

                        terms: ['c-1']

                    },

                    {

                        taxonomy: 'post_tag',

                        field: 'slug',

                        terms: ['b-1']

                    }

                ]

            }

        ]

    }

});



await abilitiesAPI.executeAbility('core/find-posts', {

    post_type: 'post',

    date_query: {

        relation: 'AND',

        queries: [

            { year: 2025 },

            {

                relation: 'OR',

                queries: [

                    { day: 26 },

                    { month: 11 }

                ]

            }

        ]

    }

});
  • Verify core/update-post modifies existing posts correctly:
await abilitiesAPI.executeAbility('core/update-post', {
    id: 327,
    content: 'Hello'
});
  • Test permission checks prevent unauthorized access, e.g., users without publish permissions trying to change status to published, etc.

@github-actions
Copy link

github-actions bot commented Dec 26, 2025

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.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Co-authored-by: jorgefilipecosta <[email protected]>
Co-authored-by: SirLouen <[email protected]>

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

@github-actions
Copy link

Flaky tests detected in e53d90f.
Some tests passed with failed attempts. The failures may not be related to this commit but are still reported for visibility. See the documentation for more information.

🔍 Workflow run URL: https://github.com/WordPress/gutenberg/actions/runs/20526671811
📝 Reported issues:

Copy link
Member

@SirLouen SirLouen left a comment

Choose a reason for hiding this comment

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

A 1400 line review 🤔

To have a little more context, is this coming from somewhere? (can you link the specific file?) Or have you copy/pasted a group of files? Maybe I'm looking wrongly, but I can't seem to find equivalents here.

The abilities can be in both Gutenberg and core. Having them in Gutenberg allows us to use them in the workflows functionality that is being worked on by @senadir and allows us to get some testing before core is released.

I wonder if this won't bring too much duplication. Wouldn't it make more sense to be a backport instead of a double port? It reminds me the compat libs but I'm not sure if this should be integrated in a similar fashion.

cc @youknowriad

@jorgefilipecosta
Copy link
Member Author

A 1400 line review 🤔

Yah it is a big change, but having the 4 abilities, ready allows us to see how reusability of artifacts happens between abilities. We can reduce the scope if people prefer, and for example start with just create and update, or find and get.

To have a little more context, is this coming from somewhere? (can you link the specific file?) Or have you copy/pasted a group of files? Maybe I'm looking wrongly, but I can't seem to find equivalents here.

The abilities can be in both Gutenberg and core. Having them in Gutenberg allows us to use them in the workflows functionality that is being worked on by @senadir and allows us to get some testing before core is released.

I wonder if this won't bring too much duplication. Wouldn't it make more sense to be a backport instead of a double port? It reminds me the compat libs but I'm not sure if this should be integrated in a similar fashion.

cc @youknowriad

The equivalents are inside https://github.com/galatanovidiu/mcp-adapter-implementation-example/tree/experiment/layerd-mcp-tools/includes/Abilities/Posts. But this is not a copy paste, is an adaptation of these abilities to core, dealing with more cases, and handling more permission scenarios like assign terms etc which were not covered. At the same time it also removes some functionalities to not have this PR even bigger, e.g: we don't have create taxonomies if they don't exist, on create post ability.

I wonder if this won't bring too much duplication. Wouldn't it make more sense to be a backport instead of a double port? It reminds me the compat libs but I'm not sure if this should be integrated in a similar fashion.

It is not a double port we are adding a PR here in Gutenberg and at the same time proposing the core backport at WordPress/wordpress-develop#10665. It is the same process followed for any other PHP change, the CI even requires a core backport for any php change on Gutenberg.

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

Labels

[Type] Feature New feature to highlight in changelogs.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants