vendredi 25 septembre 2026

À propos

Contact

Extensions

Un contrôleur REST qui pagine avec X-WP-Total sur une ressource maison

Les clients REST habitués au cœur de WordPress attendent des en-têtes précis pour paginer. Une route entièrement personnalisée doit les fournir elle-même.

Par Clément Hadrot • 14 décembre 2025 • 5 min de lecture • Aucun commentaire
Un contrôleur REST qui pagine avec X-WP-Total sur une ressource maison

Une extension d’inventaire exposait une route REST personnalisée listant des références de pièces détachées, consommée par une application mobile développée par une autre équipe. Cette équipe a rapidement signalé que leur bibliothèque cliente, habituée aux routes natives de WordPress comme /wp/v2/posts, ne parvenait pas à savoir combien de pages restaient à charger, faute d’en-têtes de pagination dans la réponse.

Ce comportement n’a rien d’un bug : les routes personnalisées enregistrées via register_rest_route() ne bénéficient d’aucune pagination automatique. Le cœur de WordPress ajoute lui-même les en-têtes X-WP-Total et X-WP-TotalPages sur ses propres contrôleurs, mais une route entièrement maison doit reproduire ce comportement explicitement.

Ce que le client REST attend

La convention établie par le cœur de WordPress, et reprise par la plupart des bibliothèques clientes construites autour de l’API REST, repose sur deux en-têtes de réponse HTTP : X-WP-Total, qui indique le nombre total d’éléments correspondant à la requête, indépendamment de la pagination appliquée, et X-WP-TotalPages, qui indique le nombre total de pages compte tenu du paramètre per_page utilisé.

Construire le contrôleur avec pagination

La route enregistrée pour cette ressource d’inventaire ressemblait à ceci avant correction, un simple retour de tableau sans aucune information de pagination :

register_rest_route( 'inventaire/v1', '/pieces', array(
    'methods'             => 'GET',
    'callback'            => 'inventaire_lister_pieces',
    'permission_callback' => 'inventaire_verifier_permission',
) );

La version corrigée calcule le total indépendamment de la page demandée, puis construit la réponse avec les en-têtes attendus, en s’appuyant sur la classe WP_REST_Response plutôt qu’un simple tableau brut :

L'essentiel à retenir : X-WP-Total et X-WP-TotalPages ne sont jamais ajoutés automatiquement hors des routes natives ; Le total doit être calculé indépendamment de la page demandée ; Les liens de navigation via l'en-tête Link facilitent l'intégration côté client
function inventaire_lister_pieces( WP_REST_Request $request ) {
    global $wpdb;

    $page     = max( 1, (int) $request->get_param( 'page' ) ?: 1 );
    $per_page = min( 100, max( 1, (int) $request->get_param( 'per_page' ) ?: 20 ) );
    $offset   = ( $page - 1 ) * $per_page;

    $total = (int) $wpdb->get_var(
        "SELECT COUNT(*) FROM {$wpdb->prefix}inventaire_pieces"
    );

    $lignes = $wpdb->get_results(
        $wpdb->prepare(
            "SELECT * FROM {$wpdb->prefix}inventaire_pieces
             ORDER BY reference ASC
             LIMIT %d OFFSET %d",
            $per_page,
            $offset
        )
    );

    $total_pages = (int) ceil( $total / $per_page );

    $response = new WP_REST_Response( $lignes );
    $response->header( 'X-WP-Total', (string) $total );
    $response->header( 'X-WP-TotalPages', (string) $total_pages );

    return $response;
}

Le calcul du total via un COUNT(*) distinct, exécuté sans les clauses LIMIT et OFFSET, est indispensable : il ne doit jamais dépendre de la page demandée, sous peine de renvoyer un total qui varie selon la position de navigation, ce qui casserait toute logique de pagination côté client.

Ajouter les liens de navigation

Au-delà des deux en-têtes de comptage, le cœur de WordPress ajoute également un en-tête Link standard, au format défini par la RFC 8288, qui fournit directement les URL de la page suivante et de la page précédente, évitant au client de les reconstruire lui-même :

$base_url = rest_url( 'inventaire/v1/pieces' );

if ( $page > 1 ) {
    $lien_precedent = add_query_arg( array( 'page' => $page - 1, 'per_page' => $per_page ), $base_url );
    $response->link_header( 'prev', $lien_precedent );
}

if ( $page < $total_pages ) {
    $lien_suivant = add_query_arg( array( 'page' => $page + 1, 'per_page' => $per_page ), $base_url );
    $response->link_header( 'next', $lien_suivant );
}

La méthode link_header(), disponible directement sur WP_REST_Response, gère automatiquement le formatage correct de l’en-tête, sans avoir à concaténer soi-même la syntaxe attendue par la RFC.

Limiter per_page à une valeur raisonnable

Un point de sécurité et de performance souvent oublié : sans limite haute explicite sur per_page, un client mal intentionné ou simplement mal configuré pourrait demander plusieurs milliers d’éléments en une seule requête, générant une charge disproportionnée côté serveur. Le plafond de 100 appliqué dans l’exemple ci-dessus reprend la convention utilisée par les routes natives de WordPress elles-mêmes.

  • Toujours calculer X-WP-Total via un comptage indépendant de la pagination appliquée, jamais via la taille du tableau de résultats retournés.
  • Ajouter systématiquement X-WP-TotalPages, calculé à partir du total et de per_page, jamais codé en dur.
  • Plafonner per_page à une valeur raisonnable, en cohérence avec les conventions déjà appliquées par le cœur de WordPress sur ses propres routes.

Un test que nous ajoutons désormais systématiquement à toute nouvelle route REST personnalisée : vérifier avec un simple appel curl -I que les en-têtes de pagination sont bien présents, avant même de tester le contenu de la réponse elle-même.

En résumé

Une route REST personnalisée n’hérite d’aucune pagination automatique, contrairement aux routes natives de WordPress. Reproduire les en-têtes X-WP-Total, X-WP-TotalPages et, dans l’idéal, Link, ne demande que quelques lignes de code supplémentaires, mais rend la ressource immédiatement compatible avec les attentes des bibliothèques clientes déjà habituées aux conventions du cœur de WordPress.

Partager :

À propos de l'auteur

Clément Hadrot

Développeur WordPress, passionné par Elementor, le FSE et l’automatisation par IA.

Voir tous ses articles

Dans la même veine

À lire aussi