Développement Web

Les hooks WordPress : actions et filtres pas à pas

13 min de lecture

📍 Guide principal : Développement WordPress : plugins, thèmes et blocs. Ce tutoriel approfondit la mécanique centrale de WordPress.

Introduction

Vous avez un squelette d’extension qui s’active proprement, mais qui ne fait rien. Le moment est venu de lui donner vie. En WordPress, on ne donne pas vie à du code en l’appelant directement : on le branche sur des événements que le cœur déclenche en permanence. Ce mécanisme de branchement, ce sont les hooks. Comprendre la différence entre une action et un filtre, savoir choisir la priorité et le bon nombre d’arguments, c’est passer du stade « je copie des extraits sans trop comprendre » à « je sais exactement où et comment intervenir ». À la fin de ce tutoriel, vous aurez ajouté à Annuaire Quartier plusieurs comportements réels, uniquement par des hooks, sans jamais toucher au cœur de WordPress.

🎯 Ce que vous allez apprendre

  • Distinguer une action (faire quelque chose) d’un filtre (transformer une valeur).
  • Brancher une fonction avec add_action() et add_filter().
  • Maîtriser la priorité et le nombre d’arguments acceptés.
  • Créer vos propres points d’extension avec do_action() et apply_filters().
  • Retirer un hook posé par un thème ou une autre extension.

🛠️ Ce que vous allez construire

Trois comportements greffés sur le cœur : une mention discrète ajoutée en pied de page du site, une note ajoutée automatiquement sous le contenu, et une feuille de style chargée proprement. Le tout sans modifier un seul fichier du noyau ni du thème — la preuve vivante que les hooks sont la bonne voie.

Prérequis

  • L’extension Annuaire Quartier en place (voir l’anatomie d’une extension).
  • WordPress 7.0 ou récent, avec WP_DEBUG activé.
  • Des bases de PHP : fonctions, arguments, valeurs de retour.
  • Test express : si vous savez écrire une fonction qui prend un argument et renvoie une valeur, vous tenez déjà l’essentiel des filtres.
  • ⏱️ Temps estimé : ~35 minutes.

Le modèle mental : actions et filtres

WordPress, en chargeant une page, traverse des centaines de points de passage. À chacun, il annonce « j’en suis là » et exécute toutes les fonctions qu’on a accrochées à ce point. Ces points de passage sont les hooks, et il en existe deux espèces aux usages bien séparés.

Une action est un signal qui dit « il se passe tel événement, fais ce que tu veux maintenant ». La fonction branchée agit — elle affiche du HTML, enregistre une donnée, envoie un courriel — et ne renvoie rien. Un filtre est différent : WordPress vous tend une valeur (le titre d’un article, le contenu, le texte d’un bouton) et vous demande « veux-tu la modifier avant que je l’utilise ? ». La fonction branchée reçoit la valeur, la transforme si besoin, et doit impérativement la renvoyer. Oublier ce return est l’erreur numéro un des débutants : le contenu disparaît, parce que le filtre a renvoyé null à la place de la valeur.

Retenez la formule : une action fait, un filtre transforme et rend. Tout le développement WordPress découle de ce partage.

Étape 1 — Une première action : une mention en pied de page

WordPress déclenche une action wp_footer juste avant la fermeture du </body> de chaque page publique. C’est l’endroit idéal pour injecter quelque chose en bas de toutes les pages. Branchons-y une fonction qui affiche une mention discrète. On place ce code dans un fichier de l’extension, par exemple includes/hooks.php, inclus depuis le fichier principal.

function aq_footer_mention() {
    echo '<p class="aq-footer">'
        . esc_html__( 'Annuaire propulsé par Annuaire Quartier', 'annuaire-quartier' )
        . '</p>';
}
add_action( 'wp_footer', 'aq_footer_mention' );

Le premier argument d’add_action() est le nom du hook, le second la fonction à exécuter. Notez l’usage d’esc_html__() : la chaîne est à la fois traduisible (le suffixe __) et échappée pour un affichage sûr (le préfixe esc_html). Rechargez n’importe quelle page du site : la mention apparaît tout en bas. Vous venez d’ajouter du contenu à l’ensemble du site sans éditer le thème.

Point d’étape — La mention s’affiche en pied de chaque page. Si elle est absente, vérifiez que le thème appelle bien wp_footer() dans son gabarit (tous les thèmes corrects le font) et que le fichier includes/hooks.php est inclus.

Étape 2 — Un premier filtre : enrichir le contenu

Passons à un filtre. the_content est le filtre par lequel passe le contenu de chaque article ou page avant son affichage. En s’y branchant, on peut ajouter du texte avant ou après le contenu. Ici, ajoutons une note sous le contenu — et observons la règle d’or du filtre.

function aq_append_note( $content ) {
    if ( is_singular( 'post' ) ) {
        $note = '<p class="aq-note">'
              . esc_html__( 'Article publié sur Annuaire Quartier.', 'annuaire-quartier' )
              . '</p>';
        $content .= $note;
    }
    return $content; // INDISPENSABLE.
}
add_filter( 'the_content', 'aq_append_note' );

La fonction reçoit $content, lui concatène une note seulement sur les articles isolés (grâce à is_singular()), puis le renvoie. Si vous retirez la ligne return $content;, le contenu de tous vos articles disparaît instantanément : le filtre aura remplacé le contenu par null. C’est exactement ce qui se produit quand un débutant « casse » mystérieusement son site avec un filtre. Le réflexe à graver : un filtre renvoie toujours une valeur, même quand il ne la modifie pas.

Étape 3 — La priorité, ou l’ordre d’exécution

Plusieurs fonctions peuvent se brancher sur le même hook. Dans quel ordre s’exécutent-elles ? C’est la priorité qui décide, troisième argument d’add_action() et add_filter(). Sa valeur par défaut est 10. Une priorité plus basse s’exécute plus tôt, une priorité plus haute plus tard. Pour s’assurer que notre note apparaisse après les modifications d’autres extensions, on lui donne une priorité élevée.

// S'exécute tard, après les filtres à priorité 10 par défaut.
add_filter( 'the_content', 'aq_append_note', 20 );

Si deux extensions se disputent l’ordre d’affichage — par exemple l’une veut ajouter un bouton de partage, l’autre une note — c’est la priorité qui tranche. Comprendre ce réglage, c’est pouvoir cohabiter avec d’autres extensions sans conflit. En cas de comportement bizarre dans l’ordre d’affichage, la priorité est le premier endroit à inspecter.

Étape 4 — Le nombre d’arguments acceptés

Certains hooks passent plusieurs valeurs à vos fonctions. Par défaut, une fonction branchée ne reçoit que le premier argument. Pour en recevoir davantage, on le déclare explicitement avec le quatrième paramètre, $accepted_args. Prenons l’action save_post, déclenchée à chaque enregistrement d’un contenu, qui passe l’identifiant du contenu et l’objet complet.

function aq_on_save_post( $post_id, $post ) {
    // Ignore les sauvegardes automatiques et les révisions.
    if ( wp_is_post_autosave( $post_id ) || wp_is_post_revision( $post_id ) ) {
        return;
    }
    // Ici, on pourrait journaliser ou recalculer une donnée.
}
add_action( 'save_post', 'aq_on_save_post', 10, 2 );

Le 10 est la priorité, le 2 indique qu’on veut recevoir deux arguments. Si vous oubliez ce 2 et que votre fonction attend $post, celui-ci sera null et votre code échouera. Le contrôle sur les sauvegardes automatiques et les révisions est un autre réflexe essentiel : save_post se déclenche très souvent, et sans ce garde-fou vous exécuteriez votre logique des dizaines de fois inutilement.

Point d’étape — Vous savez maintenant régler à la fois quand (priorité) et avec quoi (arguments) une fonction se branche. Ce sont les deux réglages qui font 90 % des problèmes d’ordre et d’arguments manquants.

Étape 5 — Charger des fichiers : le bon hook

Une erreur répandue consiste à insérer une balise <link> ou <script> à la main dans le HTML. WordPress impose une méthode propre : on met en file les ressources sur le hook wp_enqueue_scripts, et WordPress se charge de les insérer au bon endroit, sans doublon, dans le bon ordre. Chargeons une feuille de style pour Annuaire Quartier.

function aq_enqueue_assets() {
    wp_enqueue_style(
        'aq-style',
        AQ_URL . 'assets/annuaire.css',
        array(),
        AQ_VERSION
    );
}
add_action( 'wp_enqueue_scripts', 'aq_enqueue_assets' );

Le premier argument est un identifiant unique, le deuxième l’URL du fichier (construite avec notre constante AQ_URL), le troisième la liste des dépendances, le quatrième la version. Passer AQ_VERSION comme version est astucieux : à chaque montée de version de l’extension, l’URL du fichier change, ce qui force les navigateurs à recharger la feuille de style au lieu de servir une version en cache. C’est la solution propre au problème du cache qui fait croire que « les styles ne se mettent pas à jour ».

Étape 6 — Créer vos propres points d’extension

Les hooks ne sont pas réservés au cœur de WordPress : vous pouvez créer les vôtres. Cela rend votre extension extensible à son tour, ce qui est précieux quand un confrère devra l’adapter sans toucher à votre code. On déclenche une action avec do_action() et on expose une valeur filtrable avec apply_filters().

// Expose un point où d'autres peuvent agir après l'affichage d'un artisan.
do_action( 'aq_after_artisan', $artisan_id );

// Laisse modifier le nombre d'artisans affichés par défaut.
$limite = apply_filters( 'aq_artisans_par_page', 10 );

La première ligne offre à quiconque la possibilité de se brancher sur aq_after_artisan avec un add_action(). La seconde propose la valeur 10 comme limite par défaut, mais permet à une autre extension de la changer avec add_filter( 'aq_artisans_par_page', ... ). C’est précisément ce mécanisme qui fait de WordPress une plateforme : chaque valeur exposée par un filtre est une poignée tendue aux autres développeurs.

Étape 7 — Retirer un hook existant

Parfois, un thème ou une extension ajoute un comportement dont vous ne voulez pas. On le retire avec remove_action() ou remove_filter(), en fournissant exactement le même nom de fonction et la même priorité que lors de l’ajout. La subtilité tient là : si la priorité ne correspond pas, le retrait échoue silencieusement.

function aq_clean_up() {
    // Suppose qu'un thème a ajouté cette fonction en priorité 15.
    remove_action( 'wp_footer', 'theme_promo_banner', 15 );
}
add_action( 'init', 'aq_clean_up' );

On effectue le retrait sur un hook qui s’exécute après l’ajout d’origine — souvent init ou plus tard — sinon il n’y a encore rien à retirer. Si le retrait ne fonctionne pas, c’est presque toujours une question de priorité incorrecte ou de timing : on tente de retirer avant que l’ajout n’ait eu lieu.

Point d’étape — Rechargez le site : la mention de pied de page, la note sous les articles et la feuille de style sont actives, et vous savez désormais retirer un hook indésirable. Tout cela sans une ligne modifiée dans le cœur ou le thème.

🐞 Pièges fréquents

Symptôme / erreur Cause probable Correctif
Le contenu des articles disparaît Un filtre the_content sans return Toujours renvoyer la valeur, modifiée ou non
Too few arguments to function $accepted_args oublié Déclarer le bon nombre d’arguments en 4ᵉ paramètre
Deux extensions s’affichent dans le mauvais ordre Priorités identiques ou mal pensées Ajuster la priorité (plus bas = plus tôt)
remove_action() sans effet Priorité différente de l’ajout, ou trop tôt Reproduire exactement la priorité, retirer après l’ajout
Styles non rafraîchis après modification Cache navigateur, version figée Passer AQ_VERSION comme version à wp_enqueue_style()

✅ Récapitulatif

Vous savez désormais brancher du code sur le cœur de WordPress. Une action fait et ne renvoie rien ; un filtre transforme et renvoie toujours sa valeur. La priorité règle l’ordre, le quatrième paramètre règle le nombre d’arguments reçus. On charge les ressources par wp_enqueue_scripts, on expose ses propres points d’extension avec do_action() et apply_filters(), et on retire un hook avec la même priorité que son ajout. Annuaire Quartier a maintenant des comportements réels, et vous tenez la mécanique sur laquelle repose tout le reste.

🧾 Aide-mémoire

Fonction Rôle
add_action( $hook, $fn, $prio, $args ) Branche une fonction qui agit
add_filter( $hook, $fn, $prio, $args ) Branche une fonction qui transforme et renvoie
do_action( $hook, ... ) Déclenche votre propre action
apply_filters( $hook, $valeur ) Expose une valeur filtrable
remove_action() / remove_filter() Retire un hook (même priorité requise)
wp_enqueue_style() / wp_enqueue_script() Met en file une ressource proprement

💪 À vous de jouer

Branchez une action sur admin_notices qui affiche un message d’accueil dans l’administration uniquement sur les pages liées à votre extension. Le message ne doit apparaître que pour les administrateurs.

Voir une solution
function aq_admin_welcome() {
    if ( ! current_user_can( 'manage_options' ) ) {
        return;
    }
    echo '<div class="notice notice-info"><p>'
       . esc_html__( 'Bienvenue dans Annuaire Quartier.', 'annuaire-quartier' )
       . '</p></div>';
}
add_action( 'admin_notices', 'aq_admin_welcome' );

current_user_can( 'manage_options' ) limite l’affichage aux administrateurs ; la classe notice notice-info reprend le style natif des messages d’administration.

Tutoriels de la série

Pour aller plus loin

FAQ

Comment savoir si un hook est une action ou un filtre ?

La documentation officielle le précise pour chacun. En pratique : si le cœur appelle do_action(), c’est une action ; s’il appelle apply_filters(), c’est un filtre. Un filtre vous tend toujours une valeur à renvoyer ; une action ne vous en tend aucune à retourner.

Que se passe-t-il si deux fonctions ont la même priorité sur un hook ?

Elles s’exécutent dans leur ordre d’ajout. C’est un comportement à ne pas surinterpréter : si l’ordre compte vraiment, fixez des priorités différentes plutôt que de compter sur l’ordre d’enregistrement.

Puis-je brancher une méthode de classe plutôt qu’une fonction ?

Oui. On passe un tableau array( $objet, 'methode' ) comme rappel. C’est l’approche recommandée pour le code structuré en classes, car elle évite de polluer l’espace de noms global avec des fonctions.

Pourquoi ma fonction sur init s’exécute-t-elle sur chaque page ?

Parce que init se déclenche à chaque chargement, côté public comme côté administration. Si vous ne voulez agir que dans un contexte, ajoutez un test comme is_admin() ou is_singular() au début de la fonction.

Les hooks ralentissent-ils le site ?

Le mécanisme lui-même est négligeable. Ce qui peut peser, c’est le travail effectué dans vos fonctions. Une fonction branchée sur un hook très fréquent (comme init ou the_content) doit rester légère et sortir tôt quand elle n’a rien à faire.

Partager