WordPress

WordPress Abilities API for store catalogues in 2026

WordPress

The WordPress Abilities API is how I now expose catalogue jobs on a WooCommerce site as named, schema-checked units instead of one more admin-ajax handler. I am Alan Vo, a Gold Coast web developer with 18 years on business sites and shops. In 2026 this is no longer a plugin experiment. WordPress 6.9 shipped the PHP registry. WordPress 7.0 shipped the JavaScript store. Both belong on a production catalogue if you already have custom stock lookups, merchandising reports, or an internal tool that talks to products.

This note is for people who already compose a WooCommerce product page with blocks and then reach for admin-ajax.php the first time merchandising wants a SKU lookup in another app. It is also for anyone shipping a WordPress block theme who needs a machine-readable way to run a job that is not a click on the storefront.

Why the WordPress Abilities API matters on store catalogues

The WordPress Abilities API matters on store catalogues because catalogue work is a set of discrete jobs, and those jobs used to hide in isolated functions that only one plugin knew how to call. A furniture shop needs "get dimensions for this SKU". A footwear shop needs "is this last width in stock". A ticketing site needs "does this venue SKU still sell". Those are not REST collections. They are capabilities with inputs, outputs and a permission check.

WordPress 6.9, documented in the Abilities API handbook and the 6.9 Abilities API note, put that contract in core: register a category, register an ability, validate JSON Schema, run a callback. WordPress 7.0 added @wordpress/abilities and @wordpress/core-abilities, covered in the 7.0 Field Guide and the client-side Abilities API note. I do not treat that as an AI novelty. I treat it as a replacement for the fifth custom REST route that nobody documented.

The other reason is permission. A WooCommerce REST product endpoint is a collection. An ability is one job with one permission_callback. I can let a merchandiser run a read-only stock check without handing them manage_options, and refuse a destructive job from someone who should not delete products.

I still use WooCommerce REST and the WooCommerce CRUD objects for ordinary product writes. Abilities sit on top of those getters. They are not a reason to skip wc_get_product(). They are a reason to stop inventing a new URL every time someone asks the catalogue a question.

This is also not the WordPress Interactivity API. Directives update the storefront DOM. Bindings fill a paragraph from product data. Abilities are for callers that need a named action with a schema, including admin tools and authenticated automation.

WordPress Abilities API in the block editor on a laptop

How the WordPress Abilities API actually works

The WordPress Abilities API actually works as a central registry of WP_Ability objects, plus optional REST under wp-abilities/v1, plus a JavaScript store in WordPress 7.0. You do not register on init. Categories go on wp_abilities_api_categories_init with wp_register_ability_category(). Abilities go on wp_abilities_api_init with wp_register_ability(). Registering off those hooks fails and fires _doing_it_wrong().

An ability name is namespace/ability-name. Lowercase letters, numbers, dashes and one slash. I use the theme or mu-plugin slug as the namespace, then an action: skanvi/get-product-dimensions, janet/check-last-width. Categories are separate slugs such as catalogue-lookups. The category must exist before the ability that points at it.

Each ability carries a label, a description, a category, input_schema, output_schema, execute_callback and permission_callback. Schemas are a WordPress subset of JSON Schema Version 4. If the job takes input or returns a value, the schema is mandatory. The registry validates both sides. A caller cannot send a random string when you asked for an object with sku.

In the execute callback I load the product with wc_get_product() or query with wc_get_products(). Then I read through CRUD getters (get_sku(), get_stock_status(), get_weight(), attribute getters). I format money with wc_price() when the output is a price string. I never print a raw _price meta key. If the product is missing I return a WP_Error, as the 6.9 note asks, not a PHP warning in the HTML.

To run an ability in PHP I fetch it with wp_get_ability( 'namespace/name' ) and call execute() with the input. That is for other plugins on the same site, not for guests. I use wp_before_execute_ability to log who asked, not to rewrite the catalogue.

REST is opt-in. Set meta.show_in_rest to true when you register. Until you do that, the ability lives in PHP only. Once you opt in, the endpoints are:

Every one of those routes requires an authenticated user. Cookie auth with an X-WP-Nonce header is the same-origin path, as in the REST API authentication handbook. Application passwords over HTTPS are the external path. I do not put a catalogue ability on a public, unauthenticated URL.

WordPress 7.0 execution from JavaScript uses executeAbility from @wordpress/abilities. Server abilities loaded by @wordpress/core-abilities pick the HTTP method from annotations: readonly: true uses GET, destructive: true plus idempotent: true uses DELETE, everything else uses POST. I mark stock and dimension lookups readonly. Guessing the method is how you cache a write.

Developer desk with two monitors during WordPress Abilities API work

Core already enqueues @wordpress/core-abilities on admin screens. I do not enqueue that module on the public product page. Fetching every server ability into a guest PDP is not a storefront feature. For mixed hosts I guard with function_exists( 'wp_register_ability' ) before I hook registration, as the 6.9 note documents.

Production checklist for catalogue abilities

A catalogue ability is not "it returned JSON on my laptop". Work through this list before it hits a live shop.

  1. Confirm WordPress is 6.9 or newer for the PHP registry, and 7.0 if you need the client store, executeAbility, or admin discovery. Check function_exists( 'wp_register_ability' ) if the mu-plugin must load on mixed versions.
  2. Register the category on wp_abilities_api_categories_init before the ability. Empty category lists are how discoverability dies.
  3. Name the ability namespace/action. Keep the action a verb plus a noun. Do not reuse a WooCommerce REST slug as the ability name.
  4. Write input_schema and output_schema. If the job takes a SKU, require it. If it returns stock, type the fields. Schema is the documentation agents actually read.
  5. Put permission_callback on a real WooCommerce capability: manage_woocommerce or edit_products for writes, a tighter custom cap for read-only lookups if merchandisers should not open wp-admin.
  6. Load products with wc_get_product() or wc_get_products(). Return WP_Error when the SKU is missing, private, or not a product.
  7. Set meta.show_in_rest only on abilities you intend to call over HTTP. Leave internal PHP-only jobs off the REST list.
  8. Annotate readonly, destructive and idempotent so WordPress 7.0 picks GET, POST or DELETE correctly.
  9. Keep execute callbacks free of current-user HTML. Return data. Let the caller render.
  10. Test as a shop manager, a merchandiser, and an application-password user. Then test logged out and confirm 401.

I keep a staging SKU that is draft, a SKU that is out of stock, and a SKU that does not exist. Ability bugs hide in those three.

What breaks when you expose catalogue abilities

The first break is treating abilities like public storefront JSON. All wp-abilities/v1 routes need an authenticated user. If you "fix" a 401 by stripping the permission callback, you have published a catalogue RPC. I have seen people paste an application password into a theme script that runs on every PDP. That is a credential leak, not an integration.

Website wireframes on paper next to a keyboard

The second break is reading underscore meta instead of CRUD getters. _price skips tax display, sale price and currency. The real price block says one thing. Your ability says another. Merchandisers will paste the wrong number into a campaign. Load the product object. Use getters.

Other breaks I have had to unwind: registering on init instead of wp_abilities_api_init, a missing category, show_in_rest left false while a JS client calls /run, a readonly ability that still writes, HTML in the callback, @wordpress/core-abilities on the public theme, and a cache plugin storing a GET /run for the next user. Application passwords in a theme bundle belong in the same bin as a leaked deploy password.

When an ability "does nothing", I check three layers. Is it registered (wp_has_ability())? Does REST list it as that user? Does execute return WP_Error, ability_invalid_input, ability_invalid_output or ability_permission_denied? Those client codes are in the 7.0 note. Do not swallow them in a generic toast.

Variable products need an explicit rule. A parent SKU lookup that returns parent stock while add to cart uses the variation is a lie. Put parent versus variation in the input schema.

How to measure catalogue abilities

Measure whether the returned object matches the product object, not whether an AI demo looked clever. I pick a staging SKU, run the ability as a merchandiser, and compare get_sku(), get_stock_status() and the dimension attributes to the JSON. Then I change the product in admin and run it again with object cache primed.

I also check the REST method. A readonly lookup should be GET. A stock adjustment should not. I revoke any application password that was pasted into a browser snippet. Logs for /wp-json/wp-abilities/v1/ should show authenticated traffic from known tools, not anonymous GET storms.

Do not claim a conversion lift from registering an ability. I will not invent one here. The honest win is one registry, one permission check, and fewer mystery AJAX actions when the next contractor opens the theme.

Related work on this site

Catalogue abilities pay rent where photography and spec copy are the product, and where editors already live in WordPress. The Skanvi furniture storefront needs dimensions next to the gallery, which is a lookup I would rather expose as an ability than as a one-off route. The Janet Janet footwear work needs last width as catalogue data. The BridgeWorx ticketing site treats a venue line the same way. All three were contract development on an agency team. The rule is the same: read the product object, wrap the job, keep add to cart as a Woo block.

Content editor reviewing a page on a tablet

FAQ

What is the WordPress Abilities API for if I already have WooCommerce REST?

The WordPress Abilities API is for named jobs with a schema and a permission callback, not for replacing the product collection. WooCommerce REST still lists and updates products. An ability answers one question, such as dimensions for a SKU, without inventing a custom controller.

Does the WordPress Abilities API replace the Block Bindings API?

The WordPress Abilities API does not replace the WordPress Block Bindings API. Bindings fill block attributes from catalogue data on the product template. Abilities are callable units for tools and other plugins. Use both when a PDP must display a fact and an internal app must fetch the same fact.

Can guests call the WordPress Abilities API from a product page?

Guests cannot call the WordPress Abilities API REST routes. Core requires an authenticated user on every wp-abilities/v1 endpoint. Storefront clicks belong in the Interactivity API. If a guest needs a fact, print it in the HTML or bind it. Do not ship an application password to the browser.

Which WordPress version do I need for the WordPress Abilities API on a shop?

You need WordPress 6.9 for the PHP registry and optional REST, and WordPress 7.0 if you want the client-side store, annotations that pick GET or DELETE, and admin discovery through @wordpress/core-abilities. Mixed hosts should feature-detect wp_register_ability before they hook registration.

Laptop and coffee while shipping WordPress Abilities API

Keep reading

Contact if you want this kind of work on a live store.