L’Abilities API est dans le cœur de WordPress depuis la version 6.9, et 7.1 y ajoute la validation personnalisée et le suivi des appels. Pour enregistrer votre première ability, il faut passer quatre obstacles que les exemples qui circulent ne signalent pas : l’enregistrement doit se faire dans un hook précis, la propriété category est obligatoire (elle deviendra facultative avec WordPress 7.2), la catégorie doit exister avant, et elle se déclare sur une action différente. Le code complet, testé sur un WordPress 7.1 en production, est dans cet article.
Pas le temps ? Faites-le analyser par l'IA
Vous me lisez souvent ? Dites-le à Google
J'ajoute WPFormation à mes sourcesOn lit partout que l’Abilities API sert à donner des capacités aux agents IA plutôt que de leur ouvrir votre base en grand. C’est vrai, et c’est une bonne idée.
Mais entre le premier wp_register_ability() et une ability qui répond vraiment, il y a quatre portes fermées. Je les ai toutes prises dans la figure, dans l’ordre, sur un WordPress 7.1 en production. Aucune n’est mise en avant dans les tutoriels que j’ai lus, alors que deux d’entre elles figurent noir sur blanc dans la référence officielle de la fonction.
Alors voici le tutoriel que j’aurais voulu trouver : le code qui marche, les quatre messages d’erreur que vous allez croiser, et ce qu’ils veulent dire.
Une ability, c’est quoi au juste ?
Une ability est une capacité déclarée : un nom, une description, un schéma d’entrée, un schéma de sortie, une fonction qui fait le travail, et une règle de permission. Un agent qui interroge votre site voit la liste de ce qu’il peut demander, avec le format exact attendu.
En quoi c’est différent de la REST API, qui existe depuis des années ? La REST API de WordPress expose des ressources : des articles, des utilisateurs, des médias. L’agent doit deviner comment les combiner pour arriver à ses fins. Une ability expose une intention : "compter les articles", "vérifier le stock", "planifier une publication". Le schéma dit ce qu’elle attend, la permission dit qui a le droit, et vous gardez la main sur ce que la machine peut faire chez vous.
L’API PHP est arrivée dans le cœur avec WordPress 6.9, en décembre 2025. Son pendant JavaScript a suivi en 7.0, la version WordPress 7 "Armstrong". Le dépôt séparé qui servait à la développer a été archivé en février 2026 : tout vit désormais dans le cœur, et il n’y a plus rien à installer.
4 classes et 15 points d’accroche dans le cœur de WordPress
Avant d’écrire quoi que ce soit, j’ai voulu savoir à quoi je touchais. L’Abilities API pèse 2 506 lignes dans le cœur de WordPress 7.1, réparties en cinq fichiers. Plus d’un tiers tient dans une seule classe :
class-wp-ability.php 901 lignes
abilities-api.php 792 lignes
class-wp-abilities-registry.php 340 lignes
class-wp-ability-categories-registry.php 256 lignes
class-wp-ability-category.php 217 lignes
Côté objets, quatre classes seulement. Voici leurs méthodes publiques, telles que je les ai relevées :
WP_Ability get_name() get_label() get_description() get_category()
get_input_schema() get_output_schema() get_meta() get_meta_item()
normalize_input() validate_input() check_permissions() execute()
WP_Ability_Category get_slug() get_label() get_description() get_meta()
WP_Abilities_Registry register() unregister() get_all_registered()
is_registered() get_registered() get_instance()
WP_Ability_Categories_Registry les mêmes six méthodes
Une asymétrie mérite d’être notée, parce qu’elle m’a valu une erreur fatale de plus : WP_Ability possède un get_name(), mais WP_Ability_Category n’en a pas. Pour une catégorie, c’est get_slug(). Les deux objets se ressemblent assez pour qu’on suppose la même interface, et ils ne l’ont pas.
Combien de points d’accroche, au juste ? La note de version en cite deux. En cherchant les do_action et apply_filters dans les fichiers du cœur, j’en ai compté quinze :

Deux d’entre eux sont particulièrement utiles. wp_register_ability_args intercepte les propriétés de toute ability au moment de son enregistrement, y compris celles du cœur et celles des extensions : de quoi forcer une permission plus stricte sur une capacité que vous n’avez pas écrite. Et wp_get_abilities_item_include décide si une ability apparaît dans la liste, ce qui suffit à masquer une capacité sans la désenregistrer.
Les 7 nouveautés de l’API dans WordPress 7.1
La version 7.1, sortie le 19 août 2026, enrichit l’API sur sept points, et ses versions de maintenance, jusqu’à la 7.1.4 du 28 octobre, n’y ont rien changé. Deux comptent vraiment pour du code de production.
wp_ability_validate_inputetwp_ability_validate_output: deux filtres pour ajouter vos propres règles de validation, au-delà de ce que le schéma vérifie.wp_ability_invoked: une action déclenchée au début de chaque exécution, faite pour l’audit et la télémétrie.- Cinq champs de profil supplémentaires sur
core/get-user-info, plus une propriétéfieldspour ne demander qu’un sous-ensemble, et cette capacité devient joignable par REST. - Des schémas cohérents entre les capacités du cœur, avec titres et descriptions traduisibles.
- La conversion, avant exécution, de l’entrée d’un appel REST aux types déclarés dans le schéma : les chaînes deviennent entiers, booléens ou tableaux.
- Un drapeau
publicdansmeta, qui déclare en une fois qu’une capacité est destinée aux clients extérieurs (REST, adaptateurs MCP, agents) et sert de valeur par défaut àshow_in_rest. - Des critères de filtrage pour
wp_get_abilities(): par catégorie, par espace de noms ou par valeur demeta.
Sans les deux premiers, votre validation devait vivre dans la fonction d’exécution, et vous n’aviez aucun moyen propre de savoir quel agent appelait quoi.
La conversion des types suit le schéma d’entrée. Avec un champ déclaré entier, le 10 reçu dans l’adresse devient l’entier 10 ; avec un champ déclaré chaîne, il reste une chaîne. Cependant, WordPress ne convertit que ce qui passe déjà la validation : une entrée invalide reste telle quelle et part en erreur, et une ability sans schéma d’entrée reçoit la valeur brute. C’est cette valeur convertie qui arrive dans votre permission_callback, puis dans votre fonction d’exécution.
Les 4 refus que vous allez rencontrer au premier essai
J’ai lancé mon premier essai comme n’importe qui : une fonction, un tableau de propriétés, un appel. Voici ce qui s’est passé, étape par étape. Chaque message est copié de la sortie réelle.

Porte 1 : appeler la fonction au bon moment
Appelée directement, wp_register_ability() ne fait rien. Pas d’erreur fatale, pas d’avertissement visible… la fonction rend la main, et l’ability n’existe pas. Mon test affichait simplement "enregistrée : NON".
L’enregistrement doit se faire dans un callback accroché à l’action wp_abilities_api_init. Attention : en dehors de cette fenêtre, le registre refuse.
Porte 2 : la propriété category est obligatoire
Une fois dans le bon hook, deuxième refus, et cette fois avec un message :
WP_Abilities_Registry::register :
Les propriétés de l'"ability" doivent contenir une chaîne pour `category`.
La plupart des exemples que j’ai croisés donnent un tableau avec label, description, execute_callback et permission_callback. Il manque category, et sans elle rien ne s’enregistre.
Ce qui change avec WordPress 7.2 : cette porte-là va disparaître. Dans la version en développement, une ability déclarée sans category est rangée d’office dans une catégorie uncategorized fournie par le cœur. Une catégorie inexistante reste refusée, la porte suivante tient donc toujours. La 7.2 est annoncée pour début décembre 2026 ; d’ici là, sur toute la série 7.1, la propriété reste obligatoire.
Porte 3 : la catégorie doit exister avant
J’ai donc ajouté 'category' => 'site-information', un nom qui me semblait raisonnable. Troisième refus :
La catégorie de l'"ability" "site-information" n'est pas enregistrée.
Veuillez enregistrer la catégorie avant de l'assigner à l'"ability".
La catégorie n’est pas une étiquette libre mais un objet déclaré. Sur mon site, trois seulement existaient : site, user, et yoast-seo. Vous pouvez réutiliser une catégorie du cœur, ou déclarer la vôtre.
Porte 4 : la catégorie se déclare ailleurs
J’ai donc appelé wp_register_ability_category() juste avant mon ability, dans le même hook. Quatrième refus, et celui-là je ne l’avais pas vu venir :
Toutes les catégories des "abilities" doivent être inscrites
sur l'action wp_abilities_api_categories_init.
Il y a donc deux actions distinctes, et l’ordre compte : wp_abilities_api_categories_init pour les catégories, puis wp_abilities_api_init pour les abilities. Mettre les deux au même endroit ne marche pas… et c’est exactement là que j’ai perdu le plus de temps.
Les quatre refus sont silencieux côté navigateur : ils passent par le mécanisme interne de WordPress qui signale les mauvaises pratiques. Sans WP_DEBUG actif, vous ne voyez rien du tout, juste une ability qui n’existe pas. C’est la première chose à activer avant de chercher pourquoi votre code ne fait rien.
Le code complet d’une ability qui compte vos articles
Voici l’ability que j’ai fait tourner : elle compte les articles publiés, avec un filtre facultatif par catégorie. Simple… mais elle utilise tout ce qui compte, y compris les annotations que lira un agent. Placez ce fichier dans wp-content/mu-plugins/ ou dans votre extension.
<?php
/**
* Plugin Name: Ability compter les articles
*/
// 1. La catégorie, sur SON action.
add_action( 'wp_abilities_api_categories_init', function () {
wp_register_ability_category( 'wpformation', array(
'label' => 'WPFormation',
'description' => 'Capacités maison.',
) );
} );
// 2. L'ability, sur la sienne.
add_action( 'wp_abilities_api_init', function () {
wp_register_ability( 'wpformation/compter-articles', array(
'label' => 'Compter les articles publiés',
'description' => "Renvoie le nombre d'articles publiés, filtré par catégorie si besoin.",
'category' => 'wpformation',
'input_schema' => array(
'type' => 'object',
'properties' => array(
'categorie' => array( 'type' => 'string', 'description' => 'Slug de catégorie.' ),
),
'additionalProperties' => false,
),
'output_schema' => array(
'type' => 'object',
'properties' => array(
'total' => array( 'type' => 'integer' ),
'categorie' => array( 'type' => 'string' ),
),
'required' => array( 'total' ),
),
'execute_callback' => function ( $input ) {
$args = array( 'post_status' => 'publish', 'fields' => 'ids', 'posts_per_page' => 1 );
if ( ! empty( $input['categorie'] ) ) {
$args['category_name'] = $input['categorie'];
}
$q = new WP_Query( $args );
return array(
'total' => (int) $q->found_posts,
'categorie' => isset( $input['categorie'] ) ? $input['categorie'] : 'toutes',
);
},
'permission_callback' => function () {
return current_user_can( 'read' );
},
'meta' => array(
'annotations' => array( 'readonly' => true, 'destructive' => false, 'idempotent' => true ),
'public' => true,
'show_in_rest' => true,
),
) );
} );
Et ce 'public' => true, à quoi sert-il ? C’est le drapeau de la 7.1 vu plus haut : il rend show_in_rest vrai par défaut. Je laisse quand même show_in_rest explicite, pour que l’exposition HTTP se lise d’un coup d’œil.
Si les deux drapeaux se contredisent, c’est show_in_rest qui l’emporte dès que vous l’écrivez. Ainsi, une ability déclarée public avec show_in_rest à false reste hors de l’API REST. De plus, aucun des deux ne remplace la permission : ils règlent la visibilité, et le permission_callback reste le seul contrôle d’accès.
Et la permission, on la met où ? Un mot sur permission_callback. Dans mon test je l’ai ouvert pour aller vite, mais en production c’est votre garde-barrière : il décide qui peut déclencher la capacité. Une ability sans permission sérieuse revient à laisser une porte ouverte, avec une étiquette dessus.
Voir une ability dans la liste ne veut pourtant pas dire pouvoir l’exécuter. En effet, pour la liste REST, WordPress 7.1 vérifie seulement show_in_rest et le droit de lecture général du compte connecté. Il n’appelle jamais le permission_callback de chaque ability. Un simple abonné voit donc aussi les capacités dont la permission est réservée aux administrateurs, et le refus n’arrive qu’à l’appel de /run. Pour retirer une ability de toutes les listes, liste REST comprise, j’utilise le filtre wp_get_abilities_item_include. Les options item_include_callback et result_callback, elles, ne trient qu’un appel PHP précis à wp_get_abilities(). La 7.2, encore en bêta, ajoute une propriété required_capabilities : un client pourra alors demander la liste des seules capacités que son compte peut lancer (ticket Trac #66325).
Brancher la validation et la télémétrie de 7.1
Le schéma ne suffit-il pas à valider les entrées ? Non, il vérifie les types sans rien connaître du site. Mon schéma accepte n’importe quelle chaîne comme catégorie, y compris une qui n’existe pas sur le site : l’ability répond alors par un total de 0, sans signaler l’erreur. Le filtre wp_ability_validate_input va plus loin : il peut refuser une catégorie inconnue.
Je garde dans le schéma tout ce qu’il sait décrire : le type, les champs obligatoires, les valeurs permises et les limites. Le filtre ne sert qu’aux règles qui dépendent du site, comme l’existence réelle d’une catégorie. Techniquement, il peut aussi accepter une entrée que le schéma a refusée, mais cela masquerait un schéma incomplet au lieu de le corriger.
// Validation métier : au-delà du type, la réalité du site.
add_filter( 'wp_ability_validate_input', function ( $valide, $input, $ability ) {
$nom = is_object( $ability ) ? $ability->get_name() : (string) $ability;
if ( is_wp_error( $valide ) || 'wpformation/compter-articles' !== $nom || empty( $input['categorie'] ) ) {
return $valide;
}
if ( ! term_exists( $input['categorie'], 'category' ) ) {
return new WP_Error( 'categorie_inconnue',
'Catégorie inexistante : ' . $input['categorie'] );
}
return $valide;
}, 10, 3 );
// Télémétrie : une ligne de journal par appel.
add_action( 'wp_ability_invoked', function ( $ability ) {
$nom = is_object( $ability ) ? $ability->get_name() : (string) $ability;
error_log( 'ability appelée : ' . $nom );
}, 10, 1 );
Le piège des arguments : là où vous attendez l’ability, ces hooks vous passent son nom, une chaîne, pas l’objet WP_Ability. Le nom arrive en premier argument sur wp_ability_invoked, en troisième sur wp_ability_validate_input, dont le premier est le résultat de la validation en cours. J’ai écrit $ability->get_name() en lisant trop vite la description officielle, et j’ai récolté une erreur fatale, deux fois de suite. L’objet existe bien, mais plus loin dans la liste : en troisième position sur wp_ability_invoked, et pas du tout sur wp_ability_validate_input, qui ne transmet que le nom. D’où le is_object() dans le code ci-dessus : il fonctionne dans les deux cas.
Dans quel ordre se déclenchent les 8 points d’accroche ?
Reste à savoir quand ces filtres se déclenchent. J’ai accroché les huit points d’accroche du cycle d’exécution à un journal, puis appelé core/get-site-info. Le résultat n’est pas celui que j’attendais.

Regardez la position de wp_ability_invoked. Il arrive en premier, avant la normalisation, avant la validation du schéma, et avant le contrôle des droits. Autrement dit : il se déclenche même quand l’appel va être refusé pour permission insuffisante, et même quand l’entrée est invalide.
La conséquence est directe pour quiconque veut compter les appels. Ce hook mesure toutes les tentatives, réussies ou non. Si vous branchez votre journal dessus en pensant suivre l’usage réel de vos capacités, vos chiffres compteront aussi les agents qui se sont fait refouler. Pour compter les appels aboutis, c’est wp_after_execute_ability qu’il faut prendre, en huitième position.
Le couple à brancher pour un vrai journal : wp_ability_invoked pour la tentative, wp_after_execute_ability pour l’aboutissement. L’écart entre les deux compteurs vous donne exactement le nombre d’appels rejetés, et c’est souvent la ligne la plus intéressante du journal.
Pourquoi votre test échoue alors que le code est bon
Voilà le piège qui m’a coûté le plus de temps après les quatre refus, et il n’a rien à voir avec l’API.
J’ai enregistré ma capacité, elle apparaissait bien dans la liste, et l’appel me renvoyait ceci :
check_permissions() : false
execute() : WP_Error ability_invalid_permissions
L'"ability" "core/get-site-info" n'a pas les droits suffisants.
Le code n’y était pour rien. En ligne de commande, WordPress n’a aucun utilisateur connecté, donc current_user_can() répond faux quelle que soit la capacité demandée. Le même appel, précédé d’une ligne, passe sans broncher :
wp_set_current_user( 1 ); // l'identifiant d'un compte administrateur
$res = $ability->execute( array( 'fields' => array( 'name', 'version' ) ) );
// {"name":"WPFormation","version":"7.1"}
Le symptôme est trompeur parce qu’il ressemble à une erreur de déclaration : une capacité qui existe, qui refuse, et un message qui parle de droits alors que vous êtes root sur le serveur. Avant de suspecter votre permission_callback, vérifiez donc qui WordPress croit être.
Vérifiez que votre code marche vraiment
Le plus rapide est de passer par WP-CLI, sans rien écrire de définitif. Voici la sortie réelle de mon test sur le serveur, telle quelle :
enregistree : OUI
execute([]) : {"total":340,"categorie":"toutes"}
execute({"categorie":"ecosysteme-wordpress"}) : {"total":18,"categorie":"ecosysteme-wordpress"}
execute({"categorie":"nimportequoi"}) : WP_Error [categorie_inconnue]
Categorie inexistante : nimportequoi
execute({"inconnu":1}) : WP_Error [ability_invalid_input]
inconnu n'est pas une propriete valide de l'objet.
telemetrie : 4 appel(s) -> wpformation/compter-articles
total abilities : 5
Quatre lignes, quatre enseignements. L’ability répond et renvoie de vrais chiffres, ceux de mon site : 340 articles publiés, 18 dans la catégorie interrogée. Les deux refus suivants ne viennent pas du même endroit, et c’est tout l’intérêt : categorie_inconnue est ma règle métier, branchée sur le filtre de 7.1, tandis que ability_invalid_input vient du schéma lui-même, sans que j’aie rien écrit. Et la télémétrie compte bien les quatre appels, y compris les deux qui ont échoué.
Nota : sous WP-CLI, les deux actions d’initialisation sont déjà passées quand votre code s’exécute. Pour un test à la volée, il faut les rejouer avec do_action(). Dans une extension normale, vous n’avez pas ce souci.
Autre vérification utile, par exemple pour savoir ce qui tourne déjà chez vous : wp_get_abilities() retourne le registre complet, ou une partie seulement avec les critères de filtrage de la 7.1, et wp_get_ability( 'nom' ) une capacité précise.
Les 4 capacités déjà présentes sur mon site

Avant d’écrire la moindre ligne, j’ai regardé ce qui était déjà enregistré chez moi. Quatre capacités, dont une qui ne vient pas du cœur :
core/get-site-infocore/get-user-infocore/get-environment-infoyoast-seo/get-seo-scores
Yoast SEO déclare donc déjà sa propre ability, avec sa propre catégorie yoast-seo. Si vous avez Yoast, un agent qui interroge correctement votre site peut lui demander vos scores de référencement, et vous ne l’avez jamais décidé. Ce n’est pas un reproche : l’usage est exactement celui-là… mais autant le savoir.
Deuxième surprise, plus technique, et elle ne venait pas de WordPress. Sur mon propre site, l’espace de noms REST wp-abilities/v1 est bien déclaré, mais aucune route n’y répond : chaque appel renvoie une erreur "aucune route correspondante". J’ai d’abord cru que la couche PHP était là et l’exposition HTTP non. Je détaille plus bas d’où venait vraiment ce 404, avec les deux réponses côte à côte : c’est le piège dans lequel je suis tombé le premier.
Vos capacités répondent-elles en HTTP ? Oui, sauf si vous les avez bloquées vous-même
C’est le point que je cherchais depuis le début, parce qu’il décide de ce que vous pouvez faire aujourd’hui avec tout ça. Et il m’a fait tomber dans un piège que je vous raconte en entier, parce que vous risquez le même.
Les quatre capacités présentes sur mon site portent toutes show_in_rest => true. L’espace de noms wp-abilities/v1 est bien déclaré par le serveur REST. Tout indique une exposition par HTTP. J’ai donc envoyé la requête :
GET /wp-json/wp-abilities/v1
statut : 404
{"code":"rest_no_route",
"message":"Aucune route correspondante à l'URL et à la méthode de requête n'a été trouvée."}
Un espace de noms déclaré et vide. J’ai d’abord cru que le transport n’existait pas encore, puis j’ai mis ce 404 sur le compte de ma version de WordPress. C’était faux deux fois. Le cœur enregistre ces routes depuis la 6.9, et la 7.0 comme la 7.1 les ont. Ce qui les masquait, c’est un durcissement que j’avais posé moi-même : un petit fichier dans mu-plugins qui retire de l’API REST les espaces de noms que je n’utilise pas, et wp-abilities figurait dans sa liste. Je l’avais oublié. Sur une installation sans ce filtre, la réponse change du tout au tout.
GET /wp-json/wp-abilities/v1 → 200
/wp-abilities/v1 GET
/wp-abilities/v1/abilities GET
/wp-abilities/v1/abilities/<name> GET
/wp-abilities/v1/abilities/<name>/run GET, POST, PUT, PATCH, DELETE
/wp-abilities/v1/categories GET
/wp-abilities/v1/categories/<slug> GET
Six routes, dont une /run que l’index annonce avec cinq verbes. À l’exécution, une capacité n’en accepte qu’un, selon ses annotations : GET si elle est en lecture seule, DELETE si elle est destructive et idempotente, POST dans tous les autres cas. Les autres verbes reçoivent une 405 (rest_ability_invalid_method), d’après le contrôle de méthode du cœur. Les routes ne sortent pas d’un plugin, et elles ne datent pas de la 7.1 : le cœur les instancie lui-même depuis la 6.9, dans wp-includes/rest-api.php, au milieu des contrôleurs historiques, entre les collections de polices et les icônes. Trois classes tiennent la boutique : la liste, le détail par catégorie, et l’exécution.
Reste la question qui compte vraiment : qui peut appeler ces routes ? Sans authentification, la réponse est nette.
GET /wp-json/wp-abilities/v1/abilities
statut : 401
{"code":"rest_forbidden",
"message":"Désolé, vous n'avez pas l'autorisation de faire cela."}
La route existe, elle refuse simplement les anonymes. Un agent extérieur authentifié peut donc bel et bien lister vos capacités et les exécuter à distance. Il lui faut un mot de passe d’application et les droits correspondant à la capacité visée. C’est possible depuis la 6.9, sur tout site qui n’a pas filtré l’espace de noms. Ce que la 7.1 ajoute côté HTTP est plus modeste : core/get-user-info devient joignable par REST, et l’entrée d’un appel est convertie aux types du schéma avant l’exécution.
Deux conséquences pratiques. La première : un 404 sur ces routes ne vient pas de WordPress, il vient de chez vous. Cherchez une extension de sécurité ou un fichier maison qui filtre rest_endpoints, et souvenez-vous que l’espace de noms reste annoncé dans l’index même quand ses routes ont été retirées. La seconde, plus sérieuse : dès qu’une capacité est exposée en REST, par show_in_rest ou par le drapeau public de la 7.1, la permission que vous écrivez dans permission_callback n’est plus une précaution théorique. C’est elle qui décide si un appel venu d’Internet exécute votre code. Traitez-la comme une règle de sécurité, pas comme une formalité.
Faut-il pour autant tout ouvrir ? Non, et le passage par un serveur MCP garde tout son intérêt : il vous laisse choisir quelles capacités sont relayées, sous quelle identité et avec quelle journalisation, au lieu d’exposer l’ensemble du registre à quiconque détient un jeton valide. Déclarer une ability reste gagnant dans les deux cas : vous écrivez une fois le contrat de votre capacité, schémas et permissions compris, vous héritez d’une validation d’entrée de bonne qualité, et vous êtes désormais joignable par HTTP sans une ligne de code de transport.
Une capacité ou un point d’entrée REST ?
La question revient à chaque fois, alors posons les deux côte à côte. Ce sont deux outils différents, pas deux générations du même.
| Point d’entrée REST | Ability | |
|---|---|---|
| Ce qui est exposé | Une ressource | Une intention |
| Contrat d’entrée | À écrire soi-même | Schéma JSON, validé par le cœur |
| Contrat de sortie | Aucun | Schéma JSON, vérifié après exécution |
| Découverte | Index du serveur REST | wp_get_abilities(), en PHP |
| Message d’erreur | Celui que vous écrivez | Généré, traduit, avec le nom du champ fautif |
| Sécurité par défaut | permission_callback obligatoire | permission_callback et annotations |
| Appelable en HTTP | Oui | Oui depuis la 6.9, authentification requise |
La ligne qui compte est l’avant-dernière. Les annotations n’ont pas d’équivalent côté REST : une route ne dit pas d’elle-même si elle est destructive ou si on peut la rejouer sans dommage. C’est une information écrite pour des machines qui décident seules, et c’est bien ce qui change avec cette API.
Le bon moment pour s’y mettre
Mon avis, après ces essais : l’idée est bonne et la mise en œuvre est rugueuse.
Bonne, parce que déclarer des capacités vaut mieux que distribuer des clés d’API et espérer. Vous décidez de ce qu’un agent peut demander, dans quel format, et sous quelle permission. C’est une façon saine de laisser une machine travailler sur un site sans lui ouvrir la maison.
Rugueuse, parce que quatre refus successifs pour une capacité de dix lignes, c’est trois de trop. Aucun n’est difficile à corriger une fois qu’on sait. Le problème, c’est qu’on ne sait pas : les messages n’arrivent que si le mode débogage est actif. J’ai publié dix extensions WordPress, et j’ai quand même passé une bonne demi-heure sur une histoire d’action mal choisie.
Alors, pour qui c’est utile aujourd’hui ? Si vous développez une extension et que vos utilisateurs commencent à brancher des assistants sur leur site, comme je le fais moi-même avec Claude Code au quotidien, oui, déclarez vos capacités maintenant : c’est peu de code et ça vous place au bon endroit. Si vous gérez des sites clients sans développer, il n’y a rien à faire pour l’instant. Le seul geste utile : regarder qui utilise déjà cette porte chez vous, ce qu’une heure d’expertise WordPress suffit à établir.
La couche est dans le cœur, elle grossit à chaque version, et elle ne partira pas. Autant l’apprendre pendant qu’elle tient encore en cinq fichiers…
Questions fréquentes
Un agent extérieur peut-il appeler mes abilities ?
Oui, à condition qu’il soit authentifié et que la capacité soit déclarée avec show_in_rest, ou avec le drapeau public de la 7.1, qui l’active par défaut. Le cœur enregistre six routes sous wp-abilities/v1 depuis WordPress 6.9, dont une /run pour exécuter une capacité à distance. Sans authentification, la réponse est une 401 : la route existe, elle refuse les anonymes. Si vous obtenez une 404, c’est qu’un réglage de sécurité de votre site a retiré l’espace de noms : c’était mon cas, et je l’avais oublié.
Pourquoi mon test en ligne de commande refuse les droits ?
Parce qu’en ligne de commande, aucun utilisateur n’est connecté : current_user_can() répond faux quoi qu’il arrive, et l’exécution renvoie ability_invalid_permissions. Ajoutez wp_set_current_user() avec l’identifiant d’un compte administrateur avant votre appel, et le même code passe.
Quelle version de WordPress faut-il pour utiliser les abilities ?
L’API PHP est disponible depuis WordPress 6.9, avec un pendant JavaScript ajouté en 7.0. Les filtres de validation et l’action de suivi des appels présentés ici sont en revanche des nouveautés de la 7.1, sortie le 19 août 2026. Sur une version antérieure, le code s’enregistre mais la validation personnalisée ne se déclenche pas.
Mon ability ne s’enregistre pas et je n’ai aucune erreur, pourquoi ?
C’est le cas le plus fréquent, et il a quatre causes possibles : l’appel se fait hors de l’action wp_abilities_api_init, la propriété category manque (elle ne deviendra facultative qu’avec la 7.2), la catégorie indiquée n’existe pas, ou elle a été déclarée ailleurs que sur wp_abilities_api_categories_init. Activez WP_DEBUG : les quatre cas produisent un message explicite qui reste invisible sans lui.
Peut-on appeler une ability depuis l’extérieur en HTTP ?
Oui, depuis WordPress 6.9, avec une authentification : le cœur enregistre six routes sous wp-abilities/v1, dont une /run qui exécute la capacité. Vérifiez tout de même sur votre propre site avant de bâtir une intégration dessus : interrogez /wp-json/wp-abilities/v1 et regardez si des routes apparaissent. Une 401 signifie que la route existe et réclame des identifiants, une 404 qu’un filtre de votre site l’a retirée.
Faut-il déclarer sa propre catégorie ou réutiliser celles du cœur ?
Les deux fonctionnent. Le cœur fournit site et user, plus uncategorized à partir de la 7.2, et un plugin peut ajouter la sienne, comme le fait Yoast avec yoast-seo. Déclarer la vôtre, nommée d’après votre extension, évite les collisions et regroupe vos capacités. C’est quatre lignes de code sur l’action dédiée.
Quelle différence avec un serveur MCP branché sur WordPress ?
Les deux servent à donner des capacités à un agent, mais à des étages différents. Une ability est une capacité déclarée dans WordPress, réutilisable par n’importe quel consommateur. Un serveur MCP est un protocole de dialogue entre un assistant et un service. Les deux se combinent très bien : les abilities décrivent ce que le site sait faire, le serveur MCP l’expose à l’assistant. Sur ce second volet, je vous invite à lire mon guide pour connecter Claude à un site WordPress.
La chaîne WordPress + IA de ce site, en 12 étapes. Le guide vous donne la méthode pour la refaire à la main.
De la veille à la mesure, avec Claude Code, sur un WordPress classique.
- La carte des 12 étapes du workflow
- Une checklist et un prompt par étape
- 3 skills de départ à copier
- Les garde-fous nés de vrais incidents
Double opt-in : un email de confirmation à valider pour recevoir le guide. Newsletter max 2/mois. WPFormation utilise les données saisies, via Brevo, pour votre inscription et les emails annoncés, avec votre consentement. Données jamais revendues. Désabonnement en 1 clic. Vos droits et la conservation de vos données
Voir le détail du guide et de la version complète
