vendredi 25 septembre 2026

À propos

Contact

Headless & API

Types de contenu personnalisés en headless : show_in_rest et rest_base

Un CPT déclaré sans show_in_rest reste invisible pour votre front. Voici comment l'exposer correctement, avec un contrôleur adapté si besoin.

Par Clément Hadrot • 24 décembre 2020 • 5 min de lecture • Aucun commentaire
Types de contenu personnalisés en headless : show_in_rest et rest_base

Un client m’a envoyé un message inquiet un vendredi soir : son front découplé n’affichait plus les fiches de son nouveau type de contenu « Formations », créé la veille par son équipe interne. En vérifiant la déclaration du CPT, la cause est apparue immédiatement : show_in_rest n’était pas défini, donc implicitement à false. Le type de contenu existait bien dans l’administration, mais restait totalement invisible pour l’API REST, et donc pour tout front qui en dépend.

Cet article couvre l’exposition d’un CPT à l’API REST, le choix du rest_base, et les cas où un contrôleur REST personnalisé devient nécessaire — sans revenir sur la déclaration générale d’un CPT avec register_post_type(), qui est un sujet à part entière.

show_in_rest : l’argument sans lequel rien ne fonctionne

Par défaut, register_post_type() ne rend aucun type de contenu personnalisé visible dans l’API REST, contrairement aux articles et pages natifs. Il faut le déclarer explicitement :

register_post_type( 'formation', array(
    'label'        => 'Formations',
    'public'       => true,
    'show_in_rest' => true,
    'supports'     => array( 'title', 'editor', 'thumbnail', 'custom-fields' ),
    'has_archive'  => true,
) );

Avec seulement show_in_rest à true, WordPress génère automatiquement une route standard, accessible à /wp-json/wp/v2/formation par défaut (le nom de la route reprend le nom du type de contenu, au singulier, sans « s »).

Personnaliser le nom de la route avec rest_base

Le nom généré par défaut n’est pas toujours celui souhaité pour un front, en particulier lorsque le nom technique du CPT diffère du nom qu’on veut voir apparaître dans l’URL de l’API. L’argument rest_base permet de le personnaliser :

register_post_type( 'formation', array(
    'label'        => 'Formations',
    'public'       => true,
    'show_in_rest' => true,
    'rest_base'    => 'formations',
    'supports'     => array( 'title', 'editor', 'thumbnail' ),
) );

La route devient alors /wp-json/wp/v2/formations, au pluriel, cohérent avec les conventions REST habituelles (posts, pages, categories). Je recommande de toujours le déclarer explicitement, même quand le nom par défaut semble correct : cela évite une surprise si le nom technique du CPT change un jour sans que l’équipe pense à vérifier l’impact sur l’API.

Exposer les taxonomies associées

L'essentiel à retenir : show_in_rest est faux par défaut sur un CPT ; rest_base personnalise l'URL de la route ; Un contrôleur REST dédié pour un comportement sur mesure

Une taxonomie personnalisée suit exactement la même règle : sans show_in_rest, elle n’apparaît pas dans l’API, même si le CPT auquel elle est rattachée est lui-même exposé.

register_taxonomy( 'type_formation', 'formation', array(
    'label'        => 'Types de formation',
    'public'       => true,
    'show_in_rest' => true,
    'rest_base'    => 'types-formation',
    'hierarchical' => true,
) );

Une fois exposée, la taxonomie apparaît dans le champ type_formation de la réponse (sous forme de tableau d’identifiants de termes), et une route dédiée /wp-json/wp/v2/types-formation permet de récupérer la liste des termes eux-mêmes pour construire un filtre côté front.

Quand un contrôleur REST personnalisé devient utile

Le contrôleur généré automatiquement par show_in_rest convient à la majorité des cas, mais il reste générique : il applique les mêmes règles de permission et de format que n’importe quel type de contenu standard. Pour un CPT avec une logique métier propre (des champs calculés systématiques, des règles de visibilité particulières, un format de réponse différent), on peut fournir son propre contrôleur via rest_controller_class :

register_post_type( 'formation', array(
    'label'                 => 'Formations',
    'public'                => true,
    'show_in_rest'          => true,
    'rest_base'             => 'formations',
    'rest_controller_class' => 'Monsite_Formations_Controller',
) );

class Monsite_Formations_Controller extends WP_REST_Posts_Controller {
    public function prepare_item_for_response( $item, $request ) {
        $response = parent::prepare_item_for_response( $item, $request );
        $data = $response->get_data();

        $data['places_restantes'] = (int) get_post_meta( $item->ID, '_places_restantes', true );

        $response->set_data( $data );
        return $response;
    }
}

Étendre WP_REST_Posts_Controller plutôt que de repartir de zéro permet de conserver gratuitement la pagination, le filtrage par taxonomie et la gestion des permissions déjà implémentées dans le contrôleur natif, tout en ajustant uniquement ce qui doit différer.

Vérifier l’exposition sans écrire de code

Avant de déboguer côté front, une simple requête vers la racine de l’API confirme si la route existe :

curl https://exemple.fr/wp-json/ | grep formations

Si la route formations n’apparaît pas dans la liste des routes disponibles, le problème vient forcément de la déclaration du CPT côté WordPress, avant même de chercher une erreur côté front.

En résumé

Trois réglages suffisent pour exposer un CPT à un front headless : show_in_rest à true sans lequel rien n’est visible, rest_base pour maîtriser le nom de la route, et un contrôleur personnalisé uniquement lorsque le comportement par défaut ne suffit plus. Pensez systématiquement aux taxonomies associées, qui suivent exactement la même logique et sont tout aussi souvent oubliées.

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