Si vous avez déjà ouvert le code d’une extension WordPress, vous êtes forcément tombé sur des lignes commençant par add_action ou add_filter. Ce ne sont pas de simples fonctions parmi d’autres : elles forment le mécanisme central qui permet à WordPress de rester extensible sans toucher au cœur du logiciel. C’est ce mécanisme, le système de hooks, que cette page vous propose de décortiquer.
Un hook (littéralement « crochet » en anglais) est un point d’ancrage placé délibérément dans le code de WordPress, à un endroit précis de son exécution. Il permet à votre code — extension, thème ou must-use plugin — de venir s’y « accrocher » pour exécuter une action au bon moment, ou modifier une donnée avant qu’elle ne soit affichée ou enregistrée. Sans ce système, il faudrait modifier directement les fichiers du cœur pour personnaliser le moindre comportement : un cauchemar de maintenance, puisque chaque mise à jour de WordPress effacerait vos modifications.
Grâce aux hooks, le noyau reste intact, et toute la logique métier vit dans des extensions ou des thèmes qui viennent se greffer dessus. C’est ce découplage qui explique la richesse de l’écosystème WordPress : des dizaines de milliers d’extensions coexistent, chacune s’accrochant aux mêmes points d’entrée sans jamais réécrire le cœur.
Actions vs filtres : la distinction fondamentale
WordPress propose deux grandes familles de hooks, et confondre les deux est l’une des premières erreurs des développeurs débutants.
Une action permet d’exécuter du code à un moment donné, sans attente de retour. On s’en sert pour faire quelque chose : envoyer un e-mail, enregistrer une ligne en base de données, afficher du HTML, planifier une tâche. Une action ne retourne rien et n’a pas besoin de le faire.
Un filtre, à l’inverse, sert à transformer une valeur. WordPress vous confie une donnée (un titre, un contenu, un tableau d’options…), votre fonction la manipule, puis — c’est le point essentiel — doit impérativement la retourner. Si votre callback de filtre ne retourne rien, la valeur d’origine sera remplacée par null, ce qui peut casser silencieusement une page entière.
| Caractéristique | Action | Filtre |
|---|---|---|
| Fonction pour s’accrocher | add_action() | add_filter() |
| Fonction pour déclencher | do_action() | apply_filters() |
| Valeur de retour attendue | Aucune (ignorée) | La valeur transformée, obligatoirement |
| Objectif | Exécuter un effet de bord | Modifier une donnée avant usage |
| Exemples | init, wp_footer, save_post | the_content, the_title, wp_mail |
Une astuce simple pour ne plus se tromper : si dans la documentation la fonction qui déclenche le hook s’appelle do_action( 'quelque_chose' ), c’est une action. Si elle s’appelle apply_filters( 'quelque_chose', $valeur ) et que le résultat est réinjecté dans le code ($titre = apply_filters( 'the_title', $titre );), c’est un filtre.
Anatomie d’un hook : add_action, add_filter et leurs callbacks
La signature complète des deux fonctions d’accrochage est la suivante :
add_action( string $hook_name, callable $callback, int $priority = 10, int $accepted_args = 1 );
add_filter( string $hook_name, callable $callback, int $priority = 10, int $accepted_args = 1 );
- $hook_name : le nom du hook auquel on s’accroche (une chaîne de caractères).
- $callback : la fonction à exécuter.
- $priority : un entier qui détermine l’ordre d’exécution parmi les callbacks accrochés au même hook. La valeur par défaut est
10. - $accepted_args : le nombre d’arguments que WordPress doit transmettre à votre callback. Par défaut, un seul argument est transmis, même si le hook en propose davantage.
Du côté du déclenchement, le cœur (ou votre propre code) appelle :
do_action( string $hook_name, mixed ...$arg );
apply_filters( string $hook_name, mixed $value, mixed ...$arg );
Un callback peut prendre plusieurs formes. La plus courante est la fonction nommée :
function wpm_notifier_nouvel_article( $post_ID, $post, $update ) {
if ( $update ) {
return;
}
wp_mail( 'redaction@example.com', 'Nouvel article', 'ID : ' . $post_ID );
}
add_action( 'wp_insert_post', 'wpm_notifier_nouvel_article', 10, 3 );
Remarquez le 3 en dernier argument : sans lui, WordPress n’aurait transmis que $post_ID, et $post/$update seraient restés indéfinis.
On peut aussi utiliser une closure (fonction anonyme), pratique pour un traitement court sans polluer l’espace de noms global :
add_filter( 'excerpt_length', function ( $length ) {
return 20;
} );
Ou encore une méthode de classe, sous forme de tableau [ $objet, 'nom_methode' ] :
class WPM_Gestion_Commandes {
public function ajouter_meta_box() {
add_meta_box( 'wpm_commande', 'Détails commande', [ $this, 'afficher_meta_box' ], 'commande' );
}
}
$gestion = new WPM_Gestion_Commandes();
add_action( 'add_meta_boxes', [ $gestion, 'ajouter_meta_box' ] );
Ou enfin une méthode statique, référencée par [ 'NomDeClasse', 'methode_statique' ] ou par la chaîne 'NomDeClasse::methode_statique' :
class WPM_Cache {
public static function purger() {
// logique de purge
}
}
add_action( 'save_post', [ 'WPM_Cache', 'purger' ] );
Priorités, ordre d’exécution et suppression de hooks
Lorsque plusieurs callbacks sont accrochés au même hook, ils s’exécutent dans l’ordre croissant de leur priorité : un callback en priorité 5 s’exécute avant un callback en priorité 10, qui s’exécute lui-même avant un callback en priorité 20. À priorité égale, l’ordre suit celui de l’enregistrement. C’est ce paramètre qui permet, par exemple, de s’assurer qu’un traitement se déroule après celui d’une extension tierce, ou au contraire avant.
Pour retirer un callback, on utilise remove_action() ou remove_filter(), avec la même signature que l’ajout :
remove_action( 'wp_head', 'wp_generator' );
remove_filter( 'the_content', 'wpautop', 10 );
Le piège classique : pour que le retrait fonctionne, il faut fournir exactement le même nom de hook, le même callback et la même priorité que ceux utilisés lors de l’ajout. Si l’extension d’origine a accroché sa fonction en priorité 20 et que vous tentez un remove_action sans préciser cette priorité (donc avec la valeur par défaut 10), le retrait échoue silencieusement, sans erreur ni avertissement.
Autre piège : une closure anonyme ne peut pas être retirée, car il est impossible de la référencer une seconde fois de façon identique. Si une extension tierce accroche une fonction anonyme, vous ne pourrez pas la désaccrocher directement ; il faudra agir en amont ou en aval, sur un autre hook.
Le cycle de vie d’une requête WordPress
Comprendre à quel moment chaque hook se déclenche est sans doute la compétence la plus utile à acquérir. Voici les principaux hooks déclenchés lors d’une requête front-end classique, dans leur ordre réel d’exécution.
- muplugins_loaded : juste après le chargement des must-use plugins, avant les extensions classiques. Rarement utilisé.
- plugins_loaded : toutes les extensions actives sont chargées. On y initialise des fonctionnalités dépendant d’autres extensions, ou on charge les traductions avec
load_plugin_textdomain. - setup_theme : avant le chargement du thème, pour préparer des réglages qui doivent exister avant que le thème ne s’exécute.
- after_setup_theme : le
functions.phpdu thème vient d’être chargé. Endroit privilégié pouradd_theme_supportet les menus de navigation. - init : WordPress est presque entièrement initialisé. Hook le plus utilisé pour
register_post_type, les taxonomies, ou l’enregistrement de scripts. - wp_loaded : WordPress est complètement chargé, mais la requête elle-même n’a pas encore été analysée.
- parse_request : la requête entrante vient d’être analysée, avant toute résolution en objets de contenu.
- send_headers : les en-têtes HTTP sont sur le point d’être envoyés ; utile pour des en-têtes personnalisés (cache, sécurité).
- pre_get_posts : juste avant l’exécution de la requête principale (et de toute
WP_Query). Le hook de référence pour modifier les critères d’une requête, par exemple exclure une catégorie de la page d’accueil. - wp : la requête principale est résolue, l’objet
$wpest disponible ; pratique pour agir selon les conditions de page (is_singular(),is_archive()…). - template_redirect : juste avant le chargement du template, dernier moment pour rediriger ou intercepter l’affichage.
- template_include (filtre) : change le fichier de template qui sera chargé pour afficher la page.
- wp_enqueue_scripts : hook dédié à l’enregistrement des scripts et styles côté front (
wp_enqueue_script,wp_enqueue_style). - wp_head : dans la balise
<head>du thème ; pour injecter des balises meta, du CSS inline ou des scripts de suivi. - the_content (filtre) : transforme le contenu d’un article avant affichage ; très utilisé, mais à manier avec prudence pour des raisons de performance (voir plus bas).
- wp_footer : juste avant la fermeture du
</body>; emplacement classique pour les scripts de fin de page. - shutdown : le tout dernier hook déclenché avant la fin du script PHP, une fois la réponse envoyée.
Côté administration, un autre ensemble de hooks structure le chargement des écrans du tableau de bord : admin_init se déclenche à chaque chargement d’une page d’administration (utile avec l’API Settings), admin_menu permet d’ajouter des pages et sous-pages de menu avec add_menu_page et add_submenu_page, et admin_enqueue_scripts charge des scripts et styles spécifiques à l’administration, en filtrant sur l’écran courant ($hook_suffix) pour éviter de tout charger partout.
Créer ses propres hooks dans une extension ou un thème
Rien n’empêche votre extension ou votre thème de définir ses propres hooks pour devenir, à son tour, extensible par d’autres. C’est une bonne pratique dès qu’un morceau de code gagnerait à être personnalisable sans modification directe.
Quelques règles à respecter systématiquement :
- Préfixer les noms de hooks avec un identifiant propre à votre extension (par exemple
wpm_), afin d’éviter toute collision avec un hook du cœur ou d’une autre extension. - Documenter chaque hook avec un bloc de commentaire DocBlock, en précisant son rôle, ses paramètres et depuis quelle version il existe — c’est la convention suivie par le cœur de WordPress lui-même.
- Prévoir des hooks « avant/après » autour des opérations sensibles, pour permettre à d’autres codes de réagir sans devoir modifier votre logique interne :
/**
* Se déclenche avant l'enregistrement d'une commande.
*
* @param array $donnees Données brutes de la commande.
*/
do_action( 'wpm_avant_enregistrement_commande', $donnees );
$commande_id = wpm_enregistrer_commande( $donnees );
/**
* Se déclenche après l'enregistrement d'une commande.
*
* @param int $commande_id Identifiant de la commande créée.
* @param array $donnees Données brutes de la commande.
*/
do_action( 'wpm_apres_enregistrement_commande', $commande_id, $donnees );
Et pour les valeurs qui méritent d’être personnalisables, un filtre :
$taux_tva = apply_filters( 'wpm_taux_tva', 0.20, $commande_id );
Hooks dynamiques
Certains hooks intègrent une variable dans leur nom, pour cibler précisément un contexte sans avoir à filtrer manuellement dans le callback. Le cœur en propose plusieurs :
save_post_{$post_type}: se déclenche à l’enregistrement d’un contenu, uniquement pour le type de contenu concerné (par exemplesave_post_product).{$taxonomy}_add_form_fields: ajoute des champs personnalisés au formulaire de création d’un terme, pour une taxonomie donnée.
Pour s’y accrocher, il suffit de construire dynamiquement la chaîne :
add_action( 'save_post_evenement', 'wpm_valider_evenement', 10, 3 );
Pour inspecter ce qui se passe au moment de l’exécution, quatre fonctions utilitaires sont précieuses :
current_filter(): retourne le nom du hook actuellement en cours d’exécution.doing_action( $hook_name = null ): indique si une action donnée (ou n’importe quelle action si l’argument est omis) est en train de s’exécuter.did_action( $hook_name ): retourne le nombre de fois qu’une action donnée a déjà été déclenchée depuis le début de la requête.has_filter( $hook_name, $callback = false ): vérifie si un filtre (ou un callback précis sur ce filtre) est enregistré.
Hooks côté JavaScript
Le système de hooks ne se limite pas à PHP. Depuis l’arrivée de l’éditeur de blocs, WordPress fournit le paquet @wordpress/hooks, qui reproduit la même logique côté client avec les fonctions addFilter et addAction :
import { addFilter } from '@wordpress/hooks';
addFilter(
'blocks.registerBlockType',
'wpm/personnaliser-paragraphe',
( settings, name ) => {
if ( name !== 'core/paragraph' ) {
return settings;
}
settings.supports = { ...settings.supports, align: false };
return settings;
}
);
Le filtre blocks.registerBlockType illustre bien le principe : il permet de modifier les réglages d’un bloc au moment de son enregistrement, exactement comme un filtre PHP modifie une valeur avant son utilisation. On retrouve la même logique de nommage, de priorité (troisième argument optionnel) et de valeur de retour obligatoire.
Déboguer les hooks
Quand un comportement inattendu apparaît et que vous soupçonnez un hook, plusieurs outils permettent d’y voir clair.
L’extension Query Monitor est la référence : son panneau « Hooks & Actions » liste, pour la page en cours, tous les hooks déclenchés ainsi que les callbacks accrochés à chacun, avec leur priorité et l’extension ou le thème d’origine. C’est souvent le moyen le plus rapide d’identifier qui modifie quoi.
Sans outil disponible sur l’environnement, une extension-témoin minimale, écrite pour l’occasion, peut suffire : elle s’accroche au hook suspecté avec une priorité extrême (1 ou PHP_INT_MAX) et journalise le contexte avec error_log() pour confirmer si et quand il se déclenche réellement.
Enfin, WordPress expose un hook particulier, all, qui se déclenche à chaque exécution de n’importe quel hook, action ou filtre. Il peut servir à tracer l’ensemble des hooks déclenchés sur une requête, mais avec beaucoup de prudence : s’exécutant à chaque hook du cycle de vie, un callback un peu lourd accroché à all peut ralentir le site de façon spectaculaire. Il est réservé au débogage ponctuel, jamais à un usage en production.
Bonnes pratiques et pièges à éviter
- Dans un filtre, retournez toujours la valeur, même si votre condition ne s’applique pas : un
returnoublié transforme silencieusement la donnée ennull. - Évitez les requêtes SQL ou appels HTTP lourds dans
the_content: ce filtre peut s’appliquer plusieurs fois par page (widgets, flux RSS, recherche). - Vérifiez le contexte avec
is_admin()avant d’enregistrer des scripts pour le front, et avecwp_doing_ajax()pour adapter un comportement en requête AJAX. - Ne présumez jamais de l’ordre d’exécution entre extensions : fixez explicitement une priorité si l’ordre compte pour votre logique.
- Préfixez systématiquement vos hooks personnalisés pour éviter toute collision de noms.
- Passez le bon
$accepted_argsdès que votre callback a besoin de plus d’un paramètre : c’est l’erreur la plus fréquente derrière un argument « manquant ». - Une closure ne peut pas être retirée avec
remove_action/remove_filter: préférez une fonction nommée ou une méthode de classe si vous voulez garder la possibilité de la désaccrocher. - N’accrochez jamais de traitement coûteux sur des hooks très fréquents (
pre_get_posts,the_content) sans mettre en cache le résultat. - Pour retirer un hook tiers, utilisez la même priorité que celle de l’extension d’origine ; en cas de doute, inspectez le code source ou utilisez Query Monitor.
- Documentez vos propres hooks comme le fait le cœur : un futur développeur (vous y compris) vous remerciera.
Conclusion
Le système de hooks est la colonne vertébrale de WordPress : il permet d’étendre, de modifier et de personnaliser le comportement du logiciel sans jamais toucher à son cœur. Actions pour agir, filtres pour transformer et retourner une valeur, priorités pour ordonner l’exécution, et un cycle de vie de requête bien identifié pour savoir où intervenir : ces notions suffisent à comprendre la quasi-totalité des extensions et thèmes que vous rencontrerez. La meilleure façon de les maîtriser reste la pratique : accrochez-vous à un hook, inspectez le résultat avec Query Monitor, et itérez.
Pour aller plus loin, la documentation officielle reste la référence la plus fiable : le guide Hooks — Plugin Developer Handbook pour les fondamentaux, et la référence complète des hooks pour explorer, hook par hook, tous ceux que propose le cœur de WordPress.