Une alerte e-mail part trois fois pour la publication d’un seul article. Ce symptôme, un développeur WordPress finit toujours par le croiser, généralement en branchant une notification sur le hook save_post, qui semble pourtant le candidat naturel pour ce genre de besoin.
Le problème vient du fonctionnement même de save_post : ce hook se déclenche à chaque enregistrement d’un contenu, y compris lors des sauvegardes automatiques de brouillon, des révisions créées en arrière-plan, et de tout enregistrement qui ne correspond pas nécessairement à une bascule réelle de statut éditorial.
Ce que save_post ne distingue pas
Rien dans les arguments transmis à save_post n’indique directement si le statut du contenu vient de changer. Le développeur doit alors comparer manuellement l’ancien et le nouveau statut, en gérant lui-même les cas de révisions automatiques, de type de contenu à exclure, et de sauvegardes répétées liées à l’éditeur de blocs qui enregistre régulièrement en arrière-plan pendant la rédaction.
Cette logique de comparaison, réécrite à la main dans chaque fonction accrochée à save_post, devient vite source d’erreurs, avec des conditions oubliées qui déclenchent des doublons ou, à l’inverse, ratent une vraie transition.
transition_post_status : un hook dédié à la comparaison

WordPress propose un hook spécifiquement conçu pour ce besoin : transition_post_status. Il se déclenche à chaque changement d’état d’un contenu, avec trois arguments directement exploitables : le nouveau statut, l’ancien statut, et l’objet WP_Post concerné.
add_action( 'transition_post_status', function ( $nouveau_statut, $ancien_statut, $post ) {
if ( 'article' !== $post->post_type ) {
return;
}
if ( 'publish' === $nouveau_statut && 'publish' !== $ancien_statut ) {
wp_mail(
'redaction@example.com',
'Nouvel article publié',
sprintf( "L'article « %s » vient de passer en ligne.", $post->post_title )
);
}
}, 10, 3 );
La condition 'publish' !== $ancien_statut est ici essentielle : elle garantit que l’alerte ne part qu’au moment précis où l’article bascule vers l’état publié, pas à chaque enregistrement ultérieur d’un article déjà en ligne.
Pourquoi trois arguments changent tout
La grande force de ce hook tient à ce qu’il fournit à la fois l’ancien et le nouveau statut dans un seul appel, sans avoir à interroger la base de données pour retrouver l’état précédent. Cela permet de couvrir des transitions bien plus fines qu’un simple passage en « publié » :
- D’un statut
draftverspending, pour notifier un relecteur qu’un contenu attend sa validation. - D’un statut
publishverstrash, pour tracer la mise à la corbeille d’un contenu sensible. - D’un statut personnalisé vers un autre, sur un flux éditorial qui définit ses propres statuts via
register_post_status().
Un exemple avec des statuts personnalisés
Sur un projet qui gère un flux de relecture à plusieurs niveaux, un statut personnalisé en_relecture peut être déclaré, puis surveillé exactement de la même façon :
register_post_status( 'en_relecture', array(
'label' => 'En relecture',
'public' => false,
'internal' => true,
'exclude_from_search' => true,
'show_in_admin_all_list' => true,
'show_in_admin_status_list' => true,
) );
add_action( 'transition_post_status', function ( $nouveau, $ancien, $post ) {
if ( 'en_relecture' === $nouveau && 'en_relecture' !== $ancien ) {
// Notifier l'équipe de relecture désignée.
}
}, 10, 3 );
La différence avec wp_after_insert_post
Un hook plus récent, wp_after_insert_post, couvre un besoin voisin mais distinct : il se déclenche une fois qu’un contenu et toutes ses métadonnées associées ont fini d’être enregistrés, ce qui le rend plus adapté quand la logique dépend de champs personnalisés déjà à jour. transition_post_status, lui, reste focalisé sur la seule question du changement d’état, sans attendre la fin complète du processus d’enregistrement.
Sur toute notification liée à un changement d’état éditorial, comparer explicitement l’ancien et le nouveau statut évite de renvoyer la même alerte à chaque clic sur « Mettre à jour », un piège que beaucoup découvrent seulement après plusieurs plaintes de la rédaction.
En résumé
Le bon réflexe consiste à se poser la question : ce déclenchement dépend-il réellement d’un changement de statut ? Si la réponse est oui, transition_post_status évite d’écrire à la main une comparaison qui existe déjà nativement, et referme du même coup la porte aux doublons de notification qui agacent tant les équipes éditoriales.