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
- Requirements doc: Ability exposure requirements
- Slack discussion: #core-ai thread
Attachments (1)
Change History (6)
This ticket was mentioned in Slack in #core-ai by gziolo. View the logs.
7 weeks ago
#3
@
4 weeks ago
- Keywords has-patch has-unit-tests added
- Milestone Awaiting Review → 7.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.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)
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 withoutWP_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 withoutWP_DEBUG.2. The two-hook ordering is the sharpest edge.
Requiring categories on
wp_abilities_api_categories_initbefore abilities onwp_abilities_api_initis an implicit ordering contract that fails invisibly. Letwp_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, notgeneral. A respectable-sounding default becomes a dumping ground;uncategorizedsignals "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.