# « sanitize_callback et validate_callback : la nuance qui évite un 500 en REST »

> La différence exacte entre les deux arguments d'un schéma de route REST maison, illustrée par un exemple qui plante sans l'un des deux.

- Auteur : Clément Hadrot
- Publié le : 2026-01-24
- Mis à jour le : 2026-01-24
- Catégorie : Headless &amp; API
- URL : https://wpmoderne.dev.wordpress-developpement.fr/headless/sanitize-callback-validate-callback-nuance-500/

## L’essentiel

- validate_callback juge, sanitize_callback transforme
- L'ordre d'exécution compte
- Sans validation, une erreur PHP peut remonter en 500

`'validate_callback' => 'is_numeric'` — cette seule ligne, oubliée dans la déclaration d'une route REST personnalisée, a suffi à provoquer une erreur 500 en production sur un projet qui acceptait un paramètre `quantite` censé toujours être un nombre. Sans validation, une chaîne de caractères passée par erreur atteignait directement le callback principal de la route, qui tentait une opération arithmétique dessus et provoquait une erreur fatale PHP.

Ce type d'incident révèle une confusion fréquente entre deux arguments du schéma d'argument d'une route REST : `sanitize_callback` et `validate_callback`. Ils se ressemblent, s'utilisent souvent ensemble, mais remplissent deux rôles bien distincts que WordPress exécute dans un ordre précis.

## Ce que fait chacun des deux arguments

`validate_callback` répond à une question binaire : la valeur reçue est-elle acceptable ? Il doit retourner un booléen (ou un objet `WP_Error` pour une erreur détaillée). S'il retourne `false`, WordPress rejette la requête avant même d'exécuter le callback principal de la route, avec une erreur HTTP 400 propre.

`sanitize_callback`, lui, ne juge pas : il transforme. Sa mission consiste à nettoyer ou convertir la valeur reçue dans le format attendu par le callback principal, par exemple convertir une chaîne `"12"` en entier `12`, ou retirer des balises HTML indésirables d'un champ texte.

## L'ordre d'exécution, souvent mal compris

WordPress exécute d'abord `validate_callback`, puis `sanitize_callback`, uniquement si la validation a réussi. Cet ordre a une conséquence directe : `validate_callback` reçoit toujours la valeur brute, non nettoyée, telle qu'envoyée par le client. Écrire une validation qui suppose une valeur déjà nettoyée constitue une erreur fréquente.

```
register_rest_route( 'boutique/v1', '/stock', array(
    'methods'  => 'GET',
    'callback' => 'boutique_recuperer_stock',
    'args'     => array(
        'quantite' => array(
            'required'          => true,
            'validate_callback' => function( $valeur, $request, $param ) {
                if ( ! is_numeric( $valeur ) ) {
                    return new WP_Error(
                        'parametre_invalide',
                        sprintf( 'Le paramètre %s doit être numérique.', $param ),
                        array( 'status' => 400 )
                    );
                }
                return true;
            },
            'sanitize_callback' => function( $valeur ) {
                return absint( $valeur );
            },
        ),
    ),
) );
```

> L'essentiel à retenir : validate_callback juge, sanitize_callback transforme ; L'ordre d'exécution compte ; Sans validation, une erreur PHP peut remonter en 500

## Le scénario qui provoque un 500

Sans `validate_callback`, une requête `GET /wp-json/boutique/v1/stock?quantite=abc` laisse passer la chaîne `"abc"` jusqu'au `sanitize_callback`. Si celui-ci applique `absint()`, la chaîne non numérique est silencieusement convertie en `0`, ce qui masque le problème sans le signaler clairement, et peut fausser une logique métier qui ne s'attend jamais à recevoir zéro pour ce champ. Pire, si le callback principal de la route effectue une opération plus fragile qu'un simple `absint()` — un accès à un tableau indexé par cette valeur, par exemple — l'absence de validation peut se traduire par une erreur fatale PHP non interceptée, remontée au client sous la forme d'une réponse HTTP 500 peu explicite.

Ajouter un `validate_callback` transforme cette même requête invalide en une réponse 400 propre, avec un message d'erreur exploitable par le front, avant même que le code métier de la route ne soit atteint.

## Les variantes à connaître

- Pour un paramètre simple, `rest_validate_request_arg()` et `rest_sanitize_request_arg()` couvrent la majorité des cas sans callback personnalisé, en s'appuyant sur le `type` déclaré dans le schéma (`integer`, `string`, `boolean`).
- Le champ `type` du schéma, à lui seul, déclenche déjà une validation basique : un type `integer` refuse une chaîne non numérique sans callback supplémentaire.
- Combiner un `enum` avec `validate_callback` permet de restreindre un paramètre à une liste fermée de valeurs acceptées, en plus du type.

## Variante avec type déclaré, sans callback personnalisé

```
'args' => array(
    'statut' => array(
        'type' => 'string',
        'enum' => array( 'disponible', 'rupture', 'precommande' ),
    ),
),
```

Cette déclaration suffit, sans écrire de fonction, à rejeter automatiquement toute valeur en dehors des trois statuts autorisés.

## En résumé

Confondre `validate_callback` et `sanitize_callback` revient à sauter l'étape qui empêche une donnée invalide d'atteindre le code métier d'une route REST personnalisée. Le premier décide si la requête continue ; le second adapte la valeur reçue à ce dont le code a réellement besoin. Omettre le premier ne provoque pas toujours une erreur visible immédiatement, ce qui rend ce type de bug particulièrement difficile à repérer avant qu'il ne se manifeste en production sous la forme d'une erreur 500 inattendue.
