Make WordPress Core

Opened 7 weeks ago

Last modified 4 weeks ago

#65569 reviewing enhancement

Abilities API category requirement is a design question worth settling

Reported by: gziolo Owned by: gziolo
Priority: normal Milestone: 7.2
Component: Abilities API Version:
Severity: normal Keywords: 2nd-opinion has-patch has-unit-tests
Cc: Focuses:

Description

Registering an ability requires a category, the category must be registered first on its own hook, and there is no built-in catch-all to fall back on. Building real abilities surfaced how much friction this creates during first-time integration. The requirement is defensible, but the current shape makes a simple ability harder to register than it needs to be, and it is worth deciding deliberately whether that is the experience we want.

Context

The guardrails already exist. Every failure mode below emits a _doing_it_wrong() notice today: registering on the wrong hook, naming a category that is not registered, and omitting the category entirely. The catch is that _doing_it_wrong() surfaces through PHP's error channel, so it is visible in a WP_DEBUG development setup and silent in a default configuration. A developer iterating without WP_DEBUG on sees only a NULL return and has nothing to go on, which is what made the category rules feel like undocumented failures rather than a contract.

One clarification worth recording: the core registered site and user categories are available for general use.

The question

The category requirement is real, but its ergonomics pull in three directions and we should pick one on purpose rather than by default.

Grow the default categories. As core introduces new abilities, ship more built-in categories so authors have a fitting home without registering their own. This keeps categorization meaningful, but it does not help the plugin that still wants its own namespace, and it grows the surface core has to maintain.

Make the category optional. Fall back to a single catch-all when none is given, so a simple ability registers with no ceremony. This removes the sharpest edge, at the cost of weaker categorization and a bucket that tends to collect everything. The fallback name matters here, since general reads as a respectable default while uncategorized signals a junk drawer, and that choice quietly shapes how readily authors reach for it.

Keep it required, improve discoverability. Leave the contract as is and close the gap with documentation, stating plainly that these functions return NULL on failure, that WP_DEBUG surfaces the reason, and that categories register on wp_abilities_api_categories_init before abilities on wp_abilities_api_init. This preserves the design intent and costs the least, but keeps a required field that some will still read as friction.

These are not mutually exclusive. The documentation improvement is worth doing regardless of which way the requirement itself lands.

Working pattern

For reference, the sequence that registers cleanly today:

add_action( 'wp_abilities_api_categories_init', function() {
        wp_register_ability_category( 'myplugin', [
                'label'       => 'My Plugin',
                'description' => 'Abilities for my plugin.',
        ] );
} );

add_action( 'wp_abilities_api_init', function() {
        wp_register_ability( 'myplugin/get-posts', [
                'label'               => 'Get Posts',
                'description'         => 'Retrieve posts with optional filtering.',
                'category'            => 'myplugin',
                'input_schema'        => [ /* ... */ ],
                'output_schema'       => [ /* ... */ ],
                'permission_callback' => fn() => current_user_can( 'edit_posts' ),
                'execute_callback'    => function( $input ) { /* ... */ },
                'meta'                => [ 'mcp' => [ 'public' => true ] ],
        ] );
} );

References

Attachments (1)

65569-optional-category-uncategorized.diff (7.8 KB ) - added by sachinrajcp123 7 weeks ago.

Download all attachments as: .zip

Change History (6)

This ticket was mentioned in Slack in #core-ai by gziolo. View the logs.


7 weeks ago

#2 @khokansardar
7 weeks ago

Taking the 2nd-opinion. I'd reframe: this is two fixable bugs and one real taste call, not three equal options.

1. The silent failure is the actual pain, not the requirement.
A bad category returns NULL, and the only signal is _doing_it_wrong() — invisible without WP_DEBUG. Hard consequence (ability silently doesn't register) behind a dev-only signal. This is a DX bug; fix it independent of the category policy by surfacing which rule failed even without WP_DEBUG.

2. The two-hook ordering is the sharpest edge.
Requiring categories on wp_abilities_api_categories_init before abilities on wp_abilities_api_init is an implicit ordering contract that fails invisibly. Let wp_register_ability() accept a category and resolve it lazily (or auto-create a minimal one) instead of a hard pre-registration gate.

3. Required vs optional — the only real design call.
I lean optional-with-a-catch-all + docs, but name the fallback uncategorized, not general. A respectable-sounding default becomes a dumping ground; uncategorized signals "fix this" and stays an escape hatch, not the default path.

Growing default categories (option 1) is worth doing anyway but doesn't solve this — it ignores the plugin that wants its own namespace.

Suggested order: fix observability first, ship the docs, then decide required-vs-optional. The first two stand on their own and make the third much smaller.

#3 @gziolo
4 weeks ago

  • Keywords has-patch has-unit-tests added
  • Milestone Awaiting Review7.2

@khokansardar, appreciate your feedback. @sachinrajcp123, thank you for providing a working patch that implements these recommendations. It looks good based on a quick scan.

#4 @gziolo
4 weeks ago

  • Owner set to gziolo
  • Status newreviewing

This ticket was mentioned in Slack in #core-ai by gziolo. View the logs.


4 weeks ago

Note: See TracTickets for help on using tickets.