Un champ ACF nommé _id_fournisseur_interne ou _seuil_alerte_stock n’est pas fait pour atterrir dans une réponse JSON consultée par n’importe qui sur Internet. C’est pourtant ce qui arrive régulièrement quand un développeur active l’option « Afficher dans l’API REST » d’un groupe de champs ACF sans réfléchir au détail de ce qui est exposé. Cette recette explique comment ne sérialiser, champ par champ, que ce qui doit vraiment sortir vers un front headless.
Le problème posé par l’export ACF en bloc
Depuis la version 5.11 d’Advanced Custom Fields, il est possible d’activer show_in_rest au niveau d’un groupe de champs entier. C’est pratique, mais ça expose tout le groupe sous la clé acf de la réponse, sans distinction entre un champ pensé pour le front (un prix, une description) et un champ purement administratif (une note interne, un identifiant de synchronisation avec un ERP). Sur un projet récent, un client nous a signalé qu’un champ de commentaire interne destiné à l’équipe commerciale apparaissait tel quel dans les réponses de l’API consommées par son application mobile.
La recette : un contrôle champ par champ avec register_rest_field
La fonction native register_rest_field() permet d’ajouter, à un type de contenu donné, un champ précis dans la réponse REST, avec un callback de lecture entièrement maîtrisé :
add_action( 'rest_api_init', function () {
register_rest_field( 'produit', 'infos_front', array(
'get_callback' => function ( $post ) {
return array(
'prix' => (float) get_field( 'prix', $post['id'] ),
'description' => get_field( 'description_courte', $post['id'] ),
'disponible' => (bool) get_field( 'en_stock', $post['id'] ),
);
// Volontairement absent : 'seuil_alerte_stock',
// 'id_fournisseur_interne', 'note_commerciale'.
},
'schema' => array(
'description' => 'Champs ACF exposés au front public',
'type' => 'object',
),
) );
} );
Le résultat apparaît sous une clé propre, infos_front, à côté de title ou content, et ne contient que ce qui a été explicitement listé dans le tableau retourné. Aucun champ ACF non mentionné ne peut fuiter, même si un futur collègue ajoute un champ de configuration dans le même groupe ACF sans y penser.

Désactiver l’export automatique du groupe ACF
Ce contrôle fin n’a de sens que si l’option native d’ACF reste désactivée pour ce groupe. Dans l’écran de configuration du groupe de champs, il suffit de laisser show_in_rest à sa valeur par défaut (désactivée) plutôt que de l’activer, et de tout faire passer par le champ personnalisé décrit ci-dessus. C’est la garantie qu’aucune clé acf parallèle ne vienne dupliquer, en clair, les champs qu’on a justement cherché à filtrer.
Variante : un champ calculé plutôt qu’une simple recopie
Le get_callback n’est pas limité à recopier des valeurs ACF telles quelles. Il peut calculer une valeur dérivée, utile pour éviter d’exposer une logique métier sensible côté front :
'disponible' => (bool) get_field( 'en_stock', $post['id'] )
&& (int) get_field( 'quantite_stock', $post['id'] ) > 0,
Ici, le front reçoit un simple booléen disponible, jamais la quantité réelle en stock ni le seuil d’alerte configuré par l’équipe achats. Le calcul reste entièrement côté serveur, ce qui évite aussi à un front mal intentionné de reconstituer une donnée interne à partir d’une valeur brute.
Autoriser aussi l’écriture, quand c’est nécessaire
Un troisième paramètre optionnel de register_rest_field() accepte un update_callback, pour les cas où le front doit pouvoir modifier certains champs via une requête authentifiée. Ce n’est pas l’objet de cette recette, volontairement centrée sur la lecture, mais il est utile de savoir que la même granularité champ par champ s’applique aussi à l’écriture, sans jamais avoir à ouvrir tout le groupe ACF en modification.
Vérifier concrètement ce qui sort
Après ce type de changement, un simple appel curl permet de confirmer qu’aucun champ interne ne traîne encore dans la réponse :
curl -s "https://exemple.fr/wp-json/wp/v2/produit/42" | jq '.infos_front, .acf'
Si la clé acf renvoie encore quelque chose après ce changement, c’est le signe que l’option d’export du groupe ACF est restée active quelque part et doit être désactivée.
Les pièges classiques
- Oublier qu’un champ répétable ACF (type
repeater) renvoie un tableau : leget_callbackdoit filtrer chaque sous-champ, pas seulement le champ parent. - Confondre
get_field()etget_field_object(): le second renvoie aussi la configuration du champ, jamais destinée au front. - Laisser le
schemavide : sans schéma déclaré, certains clients GraphQL ou générateurs de types TypeScript ne détecteront pas correctement le champ personnalisé.
En résumé
L’option d’export en bloc d’ACF est une facilité, pas une bonne pratique pour un front headless en production. Passer par register_rest_field() avec un callback explicite demande quelques lignes de plus, mais donne un contrôle total et documenté sur ce qui quitte réellement le serveur. C’est le genre de discipline qui évite, des mois plus tard, de découvrir qu’un champ de note interne s’est baladé dans une réponse JSON accessible à n’importe qui.