# 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.

- Auteur : Clément Hadrot
- Publié le : 2020-12-24
- Mis à jour le : 2020-12-24
- Catégorie : Headless &amp; API
- URL : https://wpmoderne.dev.wordpress-developpement.fr/headless/cpt-headless-show-in-rest-rest-base/

## L’essentiel

- 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

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.
