{"id":125319,"date":"2026-08-05T11:55:27","date_gmt":"2026-08-05T11:55:27","guid":{"rendered":"https:\/\/make.wordpress.org\/core\/?p=125319"},"modified":"2026-08-05T11:55:27","modified_gmt":"2026-08-05T11:55:27","slug":"filtering-registered-abilities-with-wp_get_abilities-in-wordpress-7-1","status":"publish","type":"post","link":"https:\/\/make.wordpress.org\/core\/2026\/08\/05\/filtering-registered-abilities-with-wp_get_abilities-in-wordpress-7-1\/","title":{"rendered":"Filtering registered abilities with wp_get_abilities() in WordPress 7.1"},"content":{"rendered":"<p class=\"wp-block-paragraph\">WordPress 7.1 extends <code><a href=\"https:\/\/developer.wordpress.org\/reference\/functions\/wp_get_abilities\/\">wp_get_abilities()<\/a><\/code> with a standard way to <span tabindex='0' class='glossary-item-container'>filter<span class='glossary-item-hidden-content'><span class='glossary-item-header'>Filter<\/span> <span class='glossary-item-description'>Filters are one of the two types of Hooks <a href=\"https:\/\/codex.wordpress.org\/Plugin_API\/Hooks\">https:\/\/codex.wordpress.org\/Plugin_API\/Hooks<\/a>. They provide a way for functions to modify data of other functions. They are the counterpart to Actions. Unlike Actions, filters are meant to work in an isolated manner, and should never have side effects such as affecting global variables and output.<\/span><\/span><\/span> registered abilities.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The function now accepts an optional <code>$args<\/code> array that can filter abilities by <code>category<\/code>, <code>namespace<\/code>, or <code>metadata<\/code>. It also supports callbacks for custom per-item filtering and final result processing.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Two new WordPress filters allow plugins to influence ability retrieval across the site:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>wp_get_abilities_item_include<\/code><\/li>\n\n\n\n<li><code>wp_get_abilities_result<\/code><\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">The <span tabindex='0' class='glossary-item-container'>REST API<span class='glossary-item-hidden-content'><span class='glossary-item-header'>REST API<\/span> <span class='glossary-item-description'>The REST API is an acronym for the RESTful Application Program Interface (API) that uses HTTP requests to GET, PUT, POST and DELETE data. It is how the front end of an application (think \u201cphone app\u201d or \u201cwebsite\u201d) can communicate with the data store (think \u201cdatabase\u201d or \u201cfile system\u201d)\r<a href=\"https:\/\/developer.wordpress.org\/rest-api\/\">https:\/\/developer.wordpress.org\/rest-api\/<\/a><\/span><\/span><\/span>\u2019s abilities list controller now uses <code>wp_get_abilities()<\/code> instead of implementing <span tabindex='0' class='glossary-item-container'>category<span class='glossary-item-hidden-content'><span class='glossary-item-header'>Category<\/span> <span class='glossary-item-description'>The 'category' taxonomy lets you group posts \/ content together that share a common bond. Categories are pre-defined and broad ranging.<\/span><\/span><\/span> filtering separately. It also supports filtering abilities by namespace.<\/p>\n\n\n<nav aria-label=\"Table of Contents\" class=\"wp-block-table-of-contents\"><ol><li><a class=\"wp-block-table-of-contents__entry\" href=\"#why-was-this-change-needed\">Why was this change needed?<\/a><\/li><li><a class=\"wp-block-table-of-contents__entry\" href=\"#filtering-by-category\">Filtering by category<\/a><\/li><li><a class=\"wp-block-table-of-contents__entry\" href=\"#filtering-by-namespace\">Filtering by namespace<\/a><\/li><li><a class=\"wp-block-table-of-contents__entry\" href=\"#filtering-by-metadata\">Filtering by metadata<\/a><\/li><li><a class=\"wp-block-table-of-contents__entry\" href=\"#combining-declarative-filters\">Combining declarative filters<\/a><\/li><li><a class=\"wp-block-table-of-contents__entry\" href=\"#custom-per-item-filtering\">Custom per-item filtering<\/a><\/li><li><a class=\"wp-block-table-of-contents__entry\" href=\"#processing-the-complete-result\">Processing the complete result<\/a><\/li><li><a class=\"wp-block-table-of-contents__entry\" href=\"#new-global-filters\">New global filters<\/a><ol><li><a class=\"wp-block-table-of-contents__entry\" href=\"#wp-get-abilities-item-include\">wp_get_abilities_item_include<\/a><\/li><li><a class=\"wp-block-table-of-contents__entry\" href=\"#wp-get-abilities-result\">wp_get_abilities_result<\/a><\/li><\/ol><\/li><li><a class=\"wp-block-table-of-contents__entry\" href=\"#filtering-order\">Filtering order<\/a><\/li><li><a class=\"wp-block-table-of-contents__entry\" href=\"#discovery-over-rest\">Discovery over REST<\/a><ol><li><a class=\"wp-block-table-of-contents__entry\" href=\"#built-in-annotation-types\">Built-in annotation types<\/a><\/li><\/ol><\/li><li><a class=\"wp-block-table-of-contents__entry\" href=\"#backward-compatibility\">Backward compatibility<\/a><\/li><li><a class=\"wp-block-table-of-contents__entry\" href=\"#retrieving-the-raw-registry\">Retrieving the raw registry<\/a><\/li><li><a class=\"wp-block-table-of-contents__entry\" href=\"#filtering-does-not-replace-authorisation\">Filtering does not replace authorisation<\/a><\/li><li><a class=\"wp-block-table-of-contents__entry\" href=\"#when-to-use-each-option\">When to use each option<\/a><\/li><\/ol><\/nav>\n\n\n<h2 id=\"why-was-this-change-needed\" class=\"wp-block-heading\">Why was this change needed?<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Before WordPress 7.1, there were two ways to retrieve abilities:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: php; title: ; notranslate\" title=\"\">\n\/\/ Retrieve every registered ability.\n$abilities = wp_get_abilities();\n\n\/\/ Retrieve one named ability.\n$ability = wp_get_ability( 'my-plugin\/export-users' );\n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\"><code>wp_get_abilities()<\/code> always returned the complete registry. A caller needing a subset had to retrieve every ability and filter the result manually:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: php; title: ; notranslate\" title=\"\">\n$abilities = array_filter(\n\twp_get_abilities(),\n\tfunction ( WP_Ability $ability ): bool {\n\t\treturn 'data-export' === $ability-&gt;get_category();\n\t}\n);\n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">Several consumers developed their own versions of this pattern for category, namespace, and metadata checks. The REST abilities controller also performed its own category filtering after retrieving the complete registry.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This led to:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Duplicated filtering code.<\/li>\n\n\n\n<li>Inconsistent filtering semantics between consumers.<\/li>\n\n\n\n<li>Different behaviour between the <span tabindex='0' class='glossary-item-container'>PHP<span class='glossary-item-hidden-content'><span class='glossary-item-header'>PHP<\/span> <span class='glossary-item-description'>The web scripting language in which WordPress is primarily architected. WordPress requires PHP 7.4 or higher<\/span><\/span><\/span> and REST APIs.<\/li>\n\n\n\n<li>No standard extension points for ability selection.<\/li>\n\n\n\n<li>Additional filtering passes over the registry.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">WordPress 7.1 moves this work into <code>wp_get_abilities()<\/code>, providing one shared filtering pipeline for <span tabindex='0' class='glossary-item-container'>Core<span class='glossary-item-hidden-content'><span class='glossary-item-header'>Core<\/span> <span class='glossary-item-description'>Core is the set of software required to run WordPress. The Core Development Team builds WordPress.<\/span><\/span><\/span> and plugins.<\/p>\n\n\n\n<h2 id=\"filtering-by-category\" class=\"wp-block-heading\">Filtering by category<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Pass a category slug using the <code>category<\/code> argument:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: php; title: ; notranslate\" title=\"\">\n$abilities = wp_get_abilities(\n\tarray(\n\t\t'category' =&gt; 'data-export',\n\t)\n);\n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">The comparison is exact. Only abilities whose category exactly matches the supplied string are returned.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The category must be passed as a single string. Arrays of category slugs are not supported.<\/p>\n\n\n\n<h2 id=\"filtering-by-namespace\" class=\"wp-block-heading\">Filtering by namespace<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Use <code>namespace<\/code> to retrieve abilities registered under a particular namespace:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: php; title: ; notranslate\" title=\"\">\n$abilities = wp_get_abilities(\n\tarray(\n\t\t'namespace' =&gt; 'my-plugin',\n\t)\n);\n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">The namespace is passed without the trailing slash. Both of the following values are normalised to the same namespace:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: php; title: ; notranslate\" title=\"\">\n'namespace' =&gt; 'my-plugin',\n'namespace' =&gt; 'my-plugin\/',\n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">An ability such as <code>my-plugin\/export-users<\/code> matches, while <code>another-plugin\/export-users<\/code> does not.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Namespace matching includes the namespace delimiter. Passing <code>my-plugin<\/code> does not accidentally match an ability registered under a similarly named <code>my-plugin-extra<\/code> namespace.<\/p>\n\n\n\n<h2 id=\"filtering-by-metadata\" class=\"wp-block-heading\">Filtering by metadata<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The <code>meta<\/code> argument selects abilities whose metadata contains the specified key-value pairs:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: php; title: ; notranslate\" title=\"\">\n$abilities = wp_get_abilities(\n\tarray(\n\t\t'meta' =&gt; array(\n\t\t\t'public' =&gt; true,\n\t\t),\n\t)\n);\n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">All supplied metadata conditions must match:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: php; title: ; notranslate\" title=\"\">\n$abilities = wp_get_abilities(\n\tarray(\n\t\t'meta' =&gt; array(\n\t\t\t'public'       =&gt; true,\n\t\t\t'show_in_rest' =&gt; true,\n\t\t),\n\t)\n);\n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">Nested metadata is supported:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: php; title: ; notranslate\" title=\"\">\n$abilities = wp_get_abilities(\n\tarray(\n\t\t'meta' =&gt; array(\n\t\t\t'my_client' =&gt; array(\n\t\t\t\t'public' =&gt; true,\n\t\t\t),\n\t\t),\n\t)\n);\n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">Metadata comparisons are strict. The value <code>true<\/code> does not match <code>1<\/code>, and <code>false<\/code> does not match <code>0<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The metadata filter checks that every requested condition exists and matches. An ability may contain additional metadata that was not included in the query.<\/p>\n\n\n\n<h2 id=\"combining-declarative-filters\" class=\"wp-block-heading\">Combining declarative filters<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The <code>category<\/code>, <code>namespace<\/code>, and <code>meta<\/code> arguments can be combined:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: php; title: ; notranslate\" title=\"\">\n$abilities = wp_get_abilities(\n\tarray(\n\t\t'category'  =&gt; 'data-export',\n\t\t'namespace' =&gt; 'my-plugin',\n\t\t'meta'      =&gt; array(\n\t\t\t'public' =&gt; true,\n\t\t),\n\t)\n);\n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">Conditions are combined using <code>AND<\/code> logic. An ability must satisfy every supplied argument to be included.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In this example, the result contains only abilities that:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Belong to the <code>data-export<\/code> category.<\/li>\n\n\n\n<li>Use the <code>my-plugin<\/code> namespace.<\/li>\n\n\n\n<li>Have resolved <code>public<\/code> metadata set to <code>true<\/code>.<\/li>\n<\/ul>\n\n\n\n<h2 id=\"custom-per-item-filtering\" class=\"wp-block-heading\">Custom per-item filtering<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Conditions that cannot be expressed using the declarative arguments can be handled with <code>item_include_callback<\/code>:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: php; title: ; notranslate\" title=\"\">\n$abilities = wp_get_abilities(\n\tarray(\n\t\t'namespace'             =&gt; 'my-plugin',\n\t\t'item_include_callback' =&gt; function (\n\t\t\tWP_Ability $ability\n\t\t): bool {\n\t\t\treturn my_plugin_should_include_ability( $ability );\n\t\t},\n\t)\n);\n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">The callback runs once for every ability that passed the declarative filters. It receives the <code>WP_Ability<\/code> instance and must return a boolean:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Return <code>true<\/code> to include the ability.<\/li>\n\n\n\n<li>Return <code>false<\/code> to exclude it.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">This callback is scoped to the current <code>wp_get_abilities()<\/code> call. It does not affect ability retrieval elsewhere.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Use it for conditions such as:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Custom metadata relationships.<\/li>\n\n\n\n<li>Context-dependent visibility.<\/li>\n\n\n\n<li>Integration-specific rules.<\/li>\n\n\n\n<li>Conditions involving more than one ability property.<\/li>\n<\/ul>\n\n\n\n<h2 id=\"processing-the-complete-result\" class=\"wp-block-heading\">Processing the complete result<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Use <code>result_callback<\/code> when an operation requires the complete matched array:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: php; title: ; notranslate\" title=\"\">\n$abilities = wp_get_abilities(\n\tarray(\n\t\t'namespace'       =&gt; 'my-plugin',\n\t\t'result_callback' =&gt; function ( array $abilities ): array {\n\t\t\tuasort(\n\t\t\t\t$abilities,\n\t\t\t\tfunction (\n\t\t\t\t\tWP_Ability $first,\n\t\t\t\t\tWP_Ability $second\n\t\t\t\t): int {\n\t\t\t\t\treturn strcasecmp(\n\t\t\t\t\t\t$first-&gt;get_label(),\n\t\t\t\t\t\t$second-&gt;get_label()\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t);\n\n\t\t\treturn array_slice(\n\t\t\t\t$abilities,\n\t\t\t\t0,\n\t\t\t\t10,\n\t\t\t\ttrue\n\t\t\t);\n\t\t},\n\t)\n);\n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">The result callback runs after all per-item matching has completed. It is suitable for:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Sorting.<\/li>\n\n\n\n<li>Slicing or pagination.<\/li>\n\n\n\n<li>Reordering.<\/li>\n\n\n\n<li>Other final result transformations.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Like <code>item_include_callback<\/code>, <code>result_callback<\/code> applies only to the current function call.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Registered abilities are normally returned in an associative array keyed by ability name. When sorting or slicing the result, preserve those keys when downstream code depends on them.<\/p>\n\n\n\n<h2 id=\"new-global-filters\" class=\"wp-block-heading\">New global filters<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">WordPress 7.1 also introduces two filters for plugins that need to affect ability retrieval beyond a single call site.<\/p>\n\n\n\n<h3 id=\"wp-get-abilities-item-include\" class=\"wp-block-heading\"><code>wp_get_abilities_item_include<\/code><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The <code>wp_get_abilities_item_include<\/code> filter runs for every ability that passed the declarative conditions and the caller\u2019s <code>item_include_callback<\/code>:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: php; title: ; notranslate\" title=\"\">\nadd_filter(\n\t'wp_get_abilities_item_include',\n\tfunction (\n\t\tbool $include,\n\t\tWP_Ability $ability,\n\t\tarray $args\n\t): bool {\n\t\tif ( 'my-plugin\/private-operation' === $ability-&gt;get_name() ) {\n\t\t\treturn false;\n\t\t}\n\n\t\treturn $include;\n\t},\n\t10,\n\t3\n);\n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">The filter receives:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>$include<\/code>: Whether the ability should currently be included.<\/li>\n\n\n\n<li><code>$ability<\/code>: The ability being evaluated.<\/li>\n\n\n\n<li><code>$args<\/code>: The complete arguments passed to <code>wp_get_abilities()<\/code>.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Because declarative mismatches are removed before this filter runs, the filter cannot add an ability that failed <code>category<\/code>, <code>namespace<\/code>, or <code>meta<\/code> matching. It can influence the inclusion of abilities that have reached this stage, as well as their global exclusion.<\/p>\n\n\n\n<h3 id=\"wp-get-abilities-result\" class=\"wp-block-heading\"><code>wp_get_abilities_result<\/code><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The <code>wp_get_abilities_result<\/code> filter receives the complete result after the caller\u2019s <code>result_callback<\/code>:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: php; title: ; notranslate\" title=\"\">\nadd_filter(\n\t'wp_get_abilities_result',\n\tfunction ( array $abilities, array $args ): array {\n\t\t\/\/ Apply site-wide result processing when appropriate.\n\t\treturn $abilities;\n\t},\n\t10,\n\t2\n);\n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">The filter receives:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>$abilities<\/code>: The final matched array.<\/li>\n\n\n\n<li><code>$args<\/code>: The complete arguments passed to <code>wp_get_abilities()<\/code>.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">It can be used for site-wide sorting, reordering, or other final processing.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">These are global filters. Plugins should use them only when the behaviour is intended to affect every relevant caller. For logic that belongs to one operation, prefer <code>item_include_callback<\/code> or <code>result_callback<\/code>.<\/p>\n\n\n\n<h2 id=\"filtering-order\" class=\"wp-block-heading\">Filtering order<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The complete pipeline runs in the following order:<\/p>\n\n\n\n<ol start=\"1\" class=\"wp-block-list\">\n<li>Match the <code>category<\/code> argument.<\/li>\n\n\n\n<li>Match the <code>namespace<\/code> argument.<\/li>\n\n\n\n<li>Match the <code>meta<\/code> argument.<\/li>\n\n\n\n<li>Run <code>item_include_callback<\/code>.<\/li>\n\n\n\n<li>Apply <code>wp_get_abilities_item_include<\/code>.<\/li>\n\n\n\n<li>Add included abilities to the matched result.<\/li>\n\n\n\n<li>Run <code>result_callback<\/code> on the complete result.<\/li>\n\n\n\n<li>Apply <code>wp_get_abilities_result<\/code>.<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">The declarative checks, item callback, and item filter run within a single pass over the registry.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This avoids the separate <code>array_filter()<\/code> passes that consumers previously had to implement.<\/p>\n\n\n\n<h2 id=\"discovery-over-rest\" class=\"wp-block-heading\">Discovery over REST<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The REST collection endpoint delegates to <code>wp_get_abilities()<\/code> and exposes the declarative filters as query parameters:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nGET \/wp-json\/wp-abilities\/v1\/abilities?namespace=my-plugin\nGET \/wp-json\/wp-abilities\/v1\/abilities?category=my-plugin-content\nGET \/wp-json\/wp-abilities\/v1\/abilities?meta[annotations][readonly]=true\n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">Parameters can be combined and use the same <code>AND<\/code> logic. <\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\n?category=data-export&amp;namespace=my-plugin\n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">Every collection request also forces <code>meta.show_in_rest = true<\/code> internally. Supplying another metadata query can\u2019t reveal an ability that is hidden from REST. The endpoint still requires an authenticated WordPress user, and executing a listed ability still requires its permission callback to pass. <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Custom metadata needs a REST parameter schema if its query-string values should be coerced before strict comparison. Without one, <code>'true'<\/code> will never match boolean <code>true<\/code>. The <code>rest_abilities_collection_params<\/code> filter extends the collection argument schema:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: php; title: ; notranslate\" title=\"\">\nadd_filter(\n\t'rest_abilities_collection_params',\n\tstatic function ( array $params ): array {\n\t\t$params['meta']['properties']['my_plugin'] = array(\n\t\t\t'type'       =&gt; 'object',\n\t\t\t'properties' =&gt; array(\n\t\t\t\t'enabled' =&gt; array(\n\t\t\t\t\t'type' =&gt; 'boolean',\n\t\t\t\t),\n\t\t\t),\n\t\t);\n\t\treturn $params;\n\t}\n);\n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">After that declaration, the REST API casts <code>\"true\"<\/code> to a boolean <code>true<\/code> before the value reaches the metadata-matching logic.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nGET \/wp-json\/wp-abilities\/v1\/abilities?meta[my_plugin][enabled]=true\n<\/pre><\/div>\n\n\n<h3 id=\"built-in-annotation-types\" class=\"wp-block-heading\">Built-in annotation types<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The known <code>readonly<\/code>, <code>destructive<\/code>, and <code>idempotent<\/code> annotation values are coerced from query strings to boolean values before strict matching. Core declares the schema for these standard ability annotations.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Each accepts a boolean or <code>null<\/code>, so REST can correctly cast their query values without a <span tabindex='0' class='glossary-item-container'>plugin<span class='glossary-item-hidden-content'><span class='glossary-item-header'>Plugin<\/span> <span class='glossary-item-description'>A plugin is a piece of software containing a group of functions that can be added to a WordPress website. They can extend functionality or add new features to your WordPress websites. WordPress plugins are written in the PHP programming language and integrate seamlessly with WordPress. These can be free in the WordPress.org Plugin Directory <a href=\"https:\/\/wordpress.org\/plugins\/\">https:\/\/wordpress.org\/plugins\/<\/a> or can be cost-based plugin from a third-party.<\/span><\/span><\/span> extending the schema:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\n?meta[annotations][readonly]=true\n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">Use <code>rest_abilities_collection_params<\/code> filter when making additional metadata fields queryable, especially boolean, integer, number, array, or object values that cannot be matched correctly as untyped query strings.<\/p>\n\n\n\n<h2 id=\"backward-compatibility\" class=\"wp-block-heading\">Backward compatibility<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The <code>$args<\/code> parameter is optional:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: php; title: ; notranslate\" title=\"\">\n$abilities = wp_get_abilities();\n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">Existing calls remain valid, and the function still returns an array of <code>WP_Ability<\/code> instances keyed by ability name.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Code that manually filters the result can continue to work:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: php; title: ; notranslate\" title=\"\">\n$abilities = array_filter(\n\twp_get_abilities(),\n\t'my_plugin_filter_abilities'\n);\n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">However, plugins should migrate common category, namespace, and metadata checks to the new arguments. Doing so reduces duplicated code and allows Core and other integrations to use consistent matching behaviour.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">One behavioural detail deserves particular attention: the two new global filters run even when <code>wp_get_abilities()<\/code> is called without arguments.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">As a result, the following call now means \u201cretrieve abilities through the standard filtering pipeline\u201d:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: php; title: ; notranslate\" title=\"\">\n$abilities = wp_get_abilities();\n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">It does not necessarily mean \u201cretrieve raw registry contents,\u201d because another plugin can alter the result through <code>wp_get_abilities_item_include<\/code> and <code>wp_get_abilities_result<\/code>.<\/p>\n\n\n\n<h2 id=\"retrieving-the-raw-registry\" class=\"wp-block-heading\">Retrieving the raw registry<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Code that specifically needs the complete, unfiltered registry can use <code>WP_Abilities_Registry::get_all_registered()<\/code>:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: php; title: ; notranslate\" title=\"\">\n$registry  = WP_Abilities_Registry::get_instance();\n$abilities = $registry-&gt;get_all_registered();\n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">This bypasses:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Declarative filtering.<\/li>\n\n\n\n<li>Caller callbacks.<\/li>\n\n\n\n<li><code>wp_get_abilities_item_include<\/code><\/li>\n\n\n\n<li><code>wp_get_abilities_result<\/code><\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Most application and integration code should continue using <code>wp_get_abilities()<\/code>. Direct registry access is appropriate only when raw registered state is explicitly required, such as low-level debugging or registry inspection.<\/p>\n\n\n\n<h2 id=\"filtering-does-not-replace-authorisation\" class=\"wp-block-heading\">Filtering does not replace authorisation<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Filtering controls which abilities are returned during discovery. It does not determine whether the current user may execute an ability.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">An ability\u2019s <code>permission_callback<\/code> remains responsible for authorisation:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: php; title: ; notranslate\" title=\"\">\n'permission_callback' =&gt; function (): bool {\n\treturn current_user_can( 'manage_options' );\n},\n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">Developers should not assume that an ability returned by <code>wp_get_abilities()<\/code> is executable by the current user.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Similarly, excluding an ability from a filtered result is not a security boundary. Any sensitive operation must enforce its permissions when the ability is executed.<\/p>\n\n\n\n<h2 id=\"when-to-use-each-option\" class=\"wp-block-heading\">When to use each option<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Use declarative arguments for standard selection:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: php; title: ; notranslate\" title=\"\">\nwp_get_abilities(\n\tarray(\n\t\t'category'  =&gt; 'data-export',\n\t\t'namespace' =&gt; 'my-plugin',\n\t\t'meta'      =&gt; array(\n\t\t\t'public' =&gt; true,\n\t\t),\n\t)\n);\n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">Use <code>item_include_callback<\/code> for custom conditions that apply to one call:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: php; title: ; notranslate\" title=\"\">\nwp_get_abilities(\n\tarray(\n\t\t'item_include_callback' =&gt; 'my_plugin_should_include_ability',\n\t)\n);\n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">Use <code>result_callback<\/code> for call-specific sorting or slicing:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: php; title: ; notranslate\" title=\"\">\nwp_get_abilities(\n\tarray(\n\t\t'result_callback' =&gt; 'my_plugin_prepare_ability_results',\n\t)\n);\n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">Use <code>wp_get_abilities_item_include<\/code> or <code>wp_get_abilities_result<\/code> only for behaviour intended to affect ability retrieval across callers.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Use <code>WP_Abilities_Registry::get_all_registered()<\/code> only when code explicitly requires raw, unfiltered registry data.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Together, these changes make <code>wp_get_abilities()<\/code> the shared discovery and filtering primitive for the Abilities <span tabindex='0' class='glossary-item-container'>API<span class='glossary-item-hidden-content'><span class='glossary-item-header'>API<\/span> <span class='glossary-item-description'>An API or Application Programming Interface is a software intermediary that allows programs to interact with each other and share data in limited, clearly defined ways.<\/span><\/span><\/span>, replacing duplicated filtering implementations with a consistent, <span tabindex='0' class='glossary-item-container'>extensible<span class='glossary-item-hidden-content'><span class='glossary-item-header'>Extensible<\/span> <span class='glossary-item-description'>This is the ability to add additional functionality to the code. Plugins extend the WordPress core software.<\/span><\/span><\/span> pipeline.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">These changes were introduced in changeset <a href=\"https:\/\/core.trac.wordpress.org\/changeset\/62420\">[62420]<\/a> for <span tabindex='0' class='glossary-item-container'>Trac<span class='glossary-item-hidden-content'><span class='glossary-item-header'>Trac<\/span> <span class='glossary-item-description'>An open source project by Edgewall Software that serves as a bug tracker and project management tool for WordPress.<\/span><\/span><\/span> <span tabindex='0' class='glossary-item-container'>ticket<span class='glossary-item-hidden-content'><span class='glossary-item-header'>ticket<\/span> <span class='glossary-item-description'>Created for both bug reports and feature development on the bug tracker.<\/span><\/span><\/span> <a href=\"https:\/\/core.trac.wordpress.org\/ticket\/64990\">#64990<\/a>.<\/p>\n\n\n\n<p class=\"has-text-align-right wp-block-paragraph\"><em>Props to <a href=\"https:\/\/profiles.wordpress.org\/benjamin_zekavica\/\" class=\"mention\"><span class=\"mentions-prefix\">@<\/span>benjamin_zekavica<\/a> for peer review, and <a href=\"https:\/\/profiles.wordpress.org\/gziolo\/\" class=\"mention\"><span class=\"mentions-prefix\">@<\/span>gziolo<\/a> for review, technical guidance, and suggested improvements.<\/em><\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n<p class=\"o2-appended-tags\"><a href=\"https:\/\/make.wordpress.org\/core\/tag\/abilities-api\/\" class=\"tag\"><span class=\"tag-prefix\">#<\/span>abilities-api<\/a>, <a href=\"https:\/\/make.wordpress.org\/core\/tag\/7-1\/\" class=\"tag\"><span class=\"tag-prefix\">#<\/span>7-1<\/a>, <a href=\"https:\/\/make.wordpress.org\/core\/tag\/dev-notes\/\" class=\"tag\"><span class=\"tag-prefix\">#<\/span>dev-notes<\/a>, <a href=\"https:\/\/make.wordpress.org\/core\/tag\/dev-notes-7-1\/\" class=\"tag\"><span class=\"tag-prefix\">#<\/span>dev-notes-7-1<\/a>, <a href=\"https:\/\/make.wordpress.org\/core\/tag\/rest-api\/\" class=\"tag\"><span class=\"tag-prefix\">#<\/span>rest-api<\/a><\/p><nav class='o2-post-footer-actions'><ul class='o2-post-footer-action-row'><li class='o2-post-footer-action'><a href=\"https:\/\/login.wordpress.org\/?redirect_to=https%3A%2F%2Fmake.wordpress.org%2Fcore%2F2026%2F08%2F05%2Ffiltering-registered-abilities-with-wp_get_abilities-in-wordpress-7-1%2F%23respond&#038;locale=en_US\" title=\"Login to Reply\"  class=\"genericon  genericon-reply\"  data-action=\"login-to-reply\"  data-actionstate=\"default\" >Login to Reply<\/a><\/li><\/ul><div class='o2-post-footer-action-likes'><\/div><ul class='o2-post-footer-action-row'><\/ul><\/nav>","protected":false},"excerpt":{"rendered":"<p>WordPress 7.1 extends wp_get_abilities() with a standard way to filterFilter Filters are one of the two types of Hooks https:\/\/codex.wordpress.org\/Plugin_API\/Hooks. They provide a way for functions to modify data of other functions. They are the counterpart to Actions. Unlike Actions, filters are meant to work in an isolated manner, and should never have side effects [&hellip;]<\/p>\n","protected":false},"author":7923389,"featured_media":0,"comment_status":"open","ping_status":"","sticky":false,"template":"","format":"standard","meta":{"advanced_seo_description":"","jetpack_seo_html_title":"","jetpack_seo_noindex":false,"jetpack_seo_schema_type":"","_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_feature_clip_id":0,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_post_was_ever_published":false},"categories":[1175],"tags":[5778,5885,1443,5894,1526],"class_list":["post-125319","post","type-post","status-publish","format-standard","hentry","category-general","tag-abilities-api","tag-7-1","tag-dev-notes","tag-dev-notes-7-1","tag-rest-api","mentions-benjamin_zekavica","mentions-gziolo","author-milana_cap"],"revision_note":"","jetpack_sharing_enabled":true,"jetpack_shortlink":"https:\/\/wp.me\/p2AvED-wBh","jetpack_featured_media_url":"","_links":{"self":[{"href":"https:\/\/make.wordpress.org\/core\/wp-json\/wp\/v2\/posts\/125319","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/make.wordpress.org\/core\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/make.wordpress.org\/core\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/make.wordpress.org\/core\/wp-json\/wp\/v2\/users\/7923389"}],"replies":[{"embeddable":true,"href":"https:\/\/make.wordpress.org\/core\/wp-json\/wp\/v2\/comments?post=125319"}],"version-history":[{"count":19,"href":"https:\/\/make.wordpress.org\/core\/wp-json\/wp\/v2\/posts\/125319\/revisions"}],"predecessor-version":[{"id":125353,"href":"https:\/\/make.wordpress.org\/core\/wp-json\/wp\/v2\/posts\/125319\/revisions\/125353"}],"wp:attachment":[{"href":"https:\/\/make.wordpress.org\/core\/wp-json\/wp\/v2\/media?parent=125319"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/make.wordpress.org\/core\/wp-json\/wp\/v2\/categories?post=125319"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/make.wordpress.org\/core\/wp-json\/wp\/v2\/tags?post=125319"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}