La première route REST personnalisée que j’ai écrite validait ses arguments « à la main », avec une série de if au début du callback. Ça fonctionnait, mais chaque erreur retournait un message différent, parfois un code HTTP incohérent, et le code devenait vite illisible dès que la route acceptait plus de deux paramètres. Le tableau args de register_rest_route() existe justement pour éviter ça : il externalise la validation et le nettoyage, avant même que le callback principal ne soit appelé.
Cet article détaille comment déclarer des arguments avec un schéma JSON, la différence entre validate_callback et sanitize_callback, et les pièges que j’ai rencontrés en les combinant sur des routes réelles.
Déclarer un argument avec son schéma
Chaque entrée du tableau args décrit un paramètre attendu par la route, sous forme d’un sous-ensemble de JSON Schema : type, required, default, enum, minimum/maximum pour les nombres, et les deux callbacks qui nous intéressent ici.

Voici une route complète qui liste des événements d’un agenda. Elle déclare quatre arguments de natures différentes, ce qui permet de voir chaque mécanisme à l’œuvre :
add_action( 'rest_api_init', 'agenda_declarer_routes' );
function agenda_declarer_routes() {
register_rest_route( 'agenda/v1', '/evenements', array(
'methods' => WP_REST_Server::READABLE,
'callback' => 'agenda_lister_evenements',
'permission_callback' => '__return_true',
'args' => array(
'par_page' => array(
'description' => "Nombre d'événements par page.",
'type' => 'integer',
'default' => 10,
'minimum' => 1,
'maximum' => 50,
'validate_callback' => 'rest_validate_request_arg',
'sanitize_callback' => 'rest_sanitize_request_arg',
),
'statut' => array(
'type' => 'string',
'default' => 'a_venir',
'enum' => array( 'a_venir', 'passe', 'tous' ),
),
'depuis' => array(
'type' => 'string',
'validate_callback' => 'agenda_valider_date',
),
'recherche' => array(
'type' => 'string',
'sanitize_callback' => 'agenda_nettoyer_texte',
),
),
) );
}
function agenda_valider_date( $valeur, $requete, $cle ) {
if ( ! is_string( $valeur ) ) {
return false;
}
$date = DateTime::createFromFormat( '!Y-m-d', $valeur );
if ( false === $date || $date->format( 'Y-m-d' ) !== $valeur ) {
return new WP_Error(
'agenda_date_invalide',
sprintf( '%s doit être une date au format AAAA-MM-JJ.', $cle )
);
}
return true;
}
function agenda_nettoyer_texte( $valeur ) {
return sanitize_text_field( $valeur );
}
Les quatre arguments illustrent quatre manières de procéder. par_page nomme explicitement les deux fonctions fournies par le cœur, rest_validate_request_arg() et rest_sanitize_request_arg(), qui lisent le schéma (type, minimum, maximum) dans la déclaration de la route. statut ne déclare aucun callback : comme l’argument possède un type, WordPress applique de lui-même rest_parse_request_arg(), qui valide puis nettoie d’après le schéma. depuis remplace la validation par une fonction maison, et recherche remplace le nettoyage.
validate_callback et sanitize_callback : deux rôles distincts
La distinction est simple, mais elle se brouille à l’usage. Le validate_callback répond à la question « cette valeur est-elle acceptable ? » : il ne modifie rien, il accepte ou il refuse. Le sanitize_callback répond à la question « sous quelle forme voulez-vous la manipuler ? » : il retourne la valeur transformée (entier, chaîne débarrassée de ses balises, tableau normalisé) et n’a pas à juger.
L’ordre d’exécution, dans le cœur, est le suivant : contrôle des paramètres obligatoires (required), puis tous les validate_callback, puis tous les sanitize_callback, puis le permission_callback, et enfin le callback principal. Celui-ci ne reçoit donc que des valeurs déjà contrôlées et nettoyées, à condition que la déclaration soit complète.
Le contrat d’un validate_callback mérite d’être cité précisément : seuls false et un objet WP_Error sont considérés comme un refus. Retourner false produit le message générique « Invalid parameter. » (traduit selon la langue du site) ; retourner un WP_Error permet de fournir un message lisible, comme dans agenda_valider_date() ci-dessus. Les trois arguments transmis au callback sont la valeur, l’objet WP_REST_Request et le nom du paramètre.
Le format de l’erreur renvoyée au client
Lorsqu’au moins un argument est refusé, le callback principal n’est jamais appelé. Le client reçoit un code HTTP 400 et un corps JSON dont la clé data.params détaille chaque paramètre fautif. Pour un appel à /wp-json/agenda/v1/evenements?depuis=demain, la réponse ressemble à ceci :
{
"code": "rest_invalid_param",
"message": "Invalid parameter(s): depuis",
"data": {
"status": 400,
"params": {
"depuis": "depuis doit être une date au format AAAA-MM-JJ."
}
}
}
Cette cohérence est le principal bénéfice pour le front : un seul format d’erreur à traiter, quel que soit l’argument fautif, avec le nom du champ à surligner dans un formulaire. Le paramètre obligatoire manquant produit, lui, le code rest_missing_callback_param, également en 400.
Les pièges rencontrés en combinant les deux
- Un
sanitize_callbackmaison remplace la validation par défaut. Si vous déclarez'type' => 'integer','minimum' => 1et'sanitize_callback' => 'absint'sansvalidate_callback, la valeurabcdevient silencieusement0et la borne minimale n’est jamais contrôlée. Ajoutez'validate_callback' => 'rest_validate_request_arg'dès que vous personnalisez le nettoyage. - Un callback qui ne retourne rien valide tout. Une fonction de validation qui oublie son
returnretournenull, etnulln’est pasfalse: la valeur passe. Terminez toujours par unreturn trueexplicite. - Une valeur de type inattendu atteint votre callback. Dès que vous remplacez la validation du cœur, un paramètre envoyé sous forme de tableau (
?depuis[]=1) arrive tel quel : d’où le contrôleis_string()en tête deagenda_valider_date(). - Les fonctions natives de PHP reçoivent trois arguments. WordPress appelle le callback avec la valeur, la requête et le nom du paramètre. Une fonction interne de PHP à un seul paramètre, comme
is_numeric, peut produire un avertissement ; enveloppez-la dans une fonction à vous. - La validation ne remplace pas le
permission_callback. Une valeur bien formée n’est pas une valeur autorisée : les deux contrôles répondent à des questions différentes.
Cas concret : un agenda consommé par un front JavaScript
Sur la route ci-dessus, un appel avec par_page=-5 ou par_page=1000 transmettrait la valeur telle quelle à WP_Query si la route n’avait aucun schéma, avec des résultats difficiles à prévoir. Avec le schéma, ces appels sont refusés avant d’atteindre la requête, et la documentation de la route (visible en envoyant une requête OPTIONS sur son adresse) décrit chaque argument avec son type, ses bornes et sa valeur par défaut. Le développeur du front n’a plus à lire le code PHP pour savoir ce que la route accepte.
Un argument sans schéma est une promesse non tenue : la route prétend accepter n’importe quoi, puis laisse le hasard décider de la suite.
Conclusion
Déclarer ses arguments dans args déplace la validation là où elle appartient : hors du callback, à un endroit unique, avec des erreurs 400 uniformes. Retenez la règle de répartition (validate_callback refuse, sanitize_callback transforme), le contrat exact de retour (false ou WP_Error) et le piège du nettoyage personnalisé qui écarte la validation du schéma. Avec ces trois points, la plupart des bugs de paramètres disparaissent avant d’atteindre votre logique métier.