Développer une extension WordPress sur mesure : structure, hooks et publication d’une extension légère
1. Quand écrire sa propre extension
Dans WordPress, tout comportement personnalisé doit être accroché à un hook. Du simple changement d’adresse d’expéditeur à l’ajout d’un champ personnalisé, quelques dizaines de lignes de PHP suffisent. Le problème, c’est que beaucoup de gens commencent par chercher dans la bibliothèque d’extensions.
Pour décider entre installer une extension et l’écrire soi-même : si le besoin se limite à “modifier un comportement fixe”, comme changer l’expéditeur par défaut, ajouter un bout de JS personnalisé ou modifier la longueur de l’extrait, écrire sa propre extension est plus propre que d’en installer une tierce. Pour le cadre général du choix d’une extension, voir Stack technique pour un site WordPress indépendant.
Le point contre-intuitif : un functions.php de plus en plus long n’est pas un signe de maîtrise, c’est une bombe à retardement pour le prochain changement de thème. Lors d’une mise à jour ou d’un changement de thème, le functions.php est écrasé et tout le code disparaît. La bonne méthode : déplacer la logique personnalisée dans une extension dédiée et laisser le thème s’occuper uniquement de l’apparence.
Le nommage obéit aussi à des règles. Le nom du dossier de l’extension combine un préfixe et la fonction, par exemple crater-inquiry-source, pour éviter les doublons avec les extensions officielles. Les noms de fonctions prennent le même préfixe, crater_register_source, pour éviter les conflits avec d’autres extensions.
Les conflits de noms de fonctions sont l’erreur fatale la plus fréquente des extensions WordPress. Si deux extensions déclarent function register_source(), celle qui se charge en dernier provoque un écran blanc. Toute fonction personnalisée doit porter un préfixe unique.
| Ampleur du besoin | Forme recommandée | Nombre de fichiers |
|---|---|---|
| 1 à 3 petites modifications | Extension en fichier unique | 1 fichier php |
| 3 à 5 fonctionnalités liées | Extension en fichier unique + constantes | 1 fichier php |
| Plus de 5 fonctionnalités distinctes | Extension en arborescence de dossiers | Fichier principal + includes + assets |
| Nécessite une page de réglages | Extension en arborescence de dossiers | Ajouter admin-page.php |
2. Structure minimale d’une extension en fichier unique
Une extension WordPress fonctionnelle n’a besoin que d’un seul fichier php, avec un en-tête de commentaire au début qui indique à WP qu’il s’agit d’une extension. Placez le fichier dans wp-content/plugins/ et il apparaît dans l’administration.
L’en-tête de commentaire suit un format fixe : Plugin Name, Description, Version, Author. Sans ce commentaire, WP ne le reconnaît pas comme une extension.
Exemple 1 : changer l’expéditeur par défaut, tout le plugin tient en 10 lignes. On accroche les filtres wp_mail_from et wp_mail_from_name pour remplacer wordpress@ par noreply@example.com. Aucune page de réglages nécessaire.
Ce type de plugin uniquement à base de filtres est idéal pour démarrer. Pas de base de données, pas de page d’administration, pas de tâche planifiée : en cas de problème, on désactive le plugin et tout revient à la normale.
Si vous devez stocker de la configuration, utilisez la table options de WordPress : un enregistrement, un tableau. Ne créez pas votre propre table. Pour des réglages simples, get_option et update_option suffisent.
| Type de hook | Hooks courants | Utilité |
|---|---|---|
| Filtre (filter) | wp_mail_from、the_content | Modifier le contenu affiché |
| Action | wp_head、admin_init | Insérer du code ou déclencher une action |
| Action dans le back-office | admin_menu、admin_init | Ajouter une page de réglages, uniquement dans l’administration |
| Action côté public | wp_enqueue_scripts | Charger le CSS/JS, uniquement côté public |
L’endroit où vous accrochez un hook est décisif pour les performances. admin_init ne se déclenche que dans l’administration, wp_head uniquement au chargement des pages publiques. N’accrochez pas une logique réservée à l’administration sur init : chaque requête publique l’exécuterait pour rien.
Pour charger votre propre CSS et JS, passez par le hook wp_enqueue_scripts, jamais par un echo direct dans wp_head. Un echo direct provoque un double chargement des scripts.
Exemple 3 : le plugin doit charger un script de validation sur une page de formulaire. Utilisez wp_enqueue_script, déclarez la dépendance jquery et passez le cinquième paramètre à true pour charger le script en pied de page, sans bloquer le rendu initial.
3. Activation, désinstallation et nettoyage des données
Quand un plugin crée des tables ou écrit des options, il doit gérer l’activation et la désinstallation. Créer les tables à l’activation, les supprimer à la désinstallation, sinon la base garde des résidus après la suppression du plugin.
register_activation_hook se déclenche à l’activation du plugin : idéal pour créer des tables ou écrire les options par défaut. uninstall.php se déclenche à la suppression du plugin : idéal pour nettoyer les données. Attention, désactivation et désinstallation sont deux choses différentes : la désactivation ne supprime pas les données, seule la suppression le fait.
Exemple 2 : le plugin enregistre la provenance des demandes dans une table personnalisée. À l’activation, on crée la table avec dbDelta ; à la désinstallation, on la supprime avec DROP TABLE. Si l’utilisateur désactive puis réactive, les données sont toujours là ; s’il supprime le plugin, la table disparaît avec.
| Durée de vie | Hook | Ce qu’il faut faire |
|---|---|---|
| Activer le plugin | register_activation_hook | Créer les tables, écrire les options par défaut |
| Désactiver le plugin | register_deactivation_hook | Supprimer les tâches planifiées, sans toucher aux données |
| Supprimer le plugin | uninstall.php | Supprimer les tables et les options |
| À chaque chargement | plugins_loaded | Définir les constantes, charger le domaine de traduction |
Côté sécurité : dans un plugin personnalisé, tout traitement de saisie utilisateur doit passer par $wpdb->prepare, jamais par une concaténation SQL directe. Pour la configuration de base des rôles et des droits d’administration, voir Bonnes pratiques de sécurité WordPress. Pour la compatibilité des versions PHP, voir Mise à niveau de la version PHP d’un site indépendant.
4. Publication et maintenance : un plugin privé ne va pas sur le dépôt officiel
Un plugin privé écrit pour le site d’un seul client : inutile de le soumettre sur wordpress.org. Compressez-le en zip et importez-le depuis l’administration, ou clonez-le directement avec git dans wp-content/mu-plugins : les plugins MU sont activés par défaut et ne peuvent pas être désactivés par erreur.
La gestion des versions est indispensable. Incrémentez le numéro Version dans l’en-tête du plugin à chaque modification pour que les mises à jour apparaissent dans l’administration. Ne modifiez jamais les fichiers du plugin directement sur le site en production : travaillez en local, puis envoyez.
Plusieurs plugins développés en interne par l’équipe crater suivent cette logique, voir Stack technique crater. Ils répondent chacun à un petit problème précis et ne dépassent jamais deux fichiers.
| Mode de diffusion | Cas d’usage | Avantages | Risque |
|---|---|---|---|
| Import du zip depuis l’administration | Plugin privé livré au client | Le client peut le désactiver lui-même | Risque de suppression accidentelle |
| Répertoire mu-plugins | La logique principale ne doit pas s’arrêter | Activé par défaut, impossible à désactiver par erreur | Mise à jour manuelle |
| Soumission sur wordpress.org | Plugin public générique | Mise à jour automatique | Passage en revue et documentation à rédiger |
Ne négligez pas la gestion du numéro de version. Incrémentez le Version de l’en-tête du plugin à chaque modification pour que la mention « mise à jour disponible » apparaisse dans la liste des plugins de l’administration.
Un plugin privé que vous maintenez vous-même n’a pas de canal de mise à jour officiel : gérez-le avec git et déployez en faisant un git pull directement dans le répertoire du serveur. Ne modifiez pas les fichiers directement sur le site en production.
FAQ — Questions fréquentes
Dans quels cas écrire son propre plugin plutôt qu’installer une extension tierce ?
Peut-on écrire le code dans le functions.php du thème ?
Combien de lignes de code faut-il pour un plugin WordPress minimal qui fonctionne ?
Si le plugin crée des tables de données, faut-il les supprimer soi-même à la désinstallation ?
Faut-il soumettre un plugin privé sur wordpress.org ?
Pourquoi mon code de plugin s’exécute-t-il à chaque chargement du site ?
Pour conclure
La valeur d’un plugin maison ne réside pas dans la démonstration technique, mais dans la récupération d’une petite logique maîtrisée depuis une bibliothèque de plugins tierce. Une mise à jour tierce peut changer le comportement, votre propre code reste toujours entre vos mains.
Trois étapes concrètes : déplacez une à une la logique personnalisée de functions.php vers un plugin à fichier unique ; si vous créez des tables, écrivez un uninstall pour les nettoyer ; pour un plugin privé, faites un zip ou placez-le dans mu-plugins, sans le soumettre à la bibliothèque officielle.
Une question fréquente : faut-il utiliser Composer ou un framework pour écrire un plugin ? Pour un plugin léger destiné à un seul site client, non.
Ajouter un framework signifie un dossier vendor de plusieurs dizaines de Mo et une gestion des dépendances à chaque mise à jour. Pour 300 lignes de code, le framework ne fait que compliquer les choses.
Ce n’est que lorsque le plugin devient un produit public, avec des tests unitaires et un travail à plusieurs, que Composer et les namespaces se justifient.
Un point de plus sur la sécurité : pour traiter un formulaire d’administration dans un plugin, il faut vérifier le nonce avec check_admin_referer pour éviter les falsifications de requête intersites. C’est le geste standard des formulaires d’administration WordPress, l’oublier revient à exposer l’interface d’administration.
Pour afficher une saisie utilisateur sur une page, utilisez les fonctions d’échappement esc_html, esc_attr, etc., n’utilisez pas echo directement. Même une valeur de configuration saisie dans votre propre administration doit être échappée à l’affichage public.
Un dernier point sur l’ordre d’apprentissage. Pour un premier plugin, commencez par de simples filtres comme changer l’expéditeur ou la longueur de l’extrait, sans toucher à la base de données ni aux pages d’administration.
Faites tourner une fois la boucle complète « écrire le code, envoyer, activer, voir le résultat », puis ajoutez des fonctionnalités progressivement. Commencer directement par un plugin avec tables de données et pages d’administration, c’est se perdre dans les hooks et le cycle de vie.
Écrire des plugins de plus en plus petits, c’est la bonne direction. Avant chaque fonctionnalité, demandez-vous si elle tient en vingt lignes avec les hooks existants. Si oui, pas de table, pas de page d’administration.
Garder le code d’un plugin sous 300 lignes, c’est une responsabilité envers soi-même et envers le client. Au-delà, il vaut mieux utiliser un plugin tiers éprouvé.
Un dernier rappel : n’empilez pas la logique personnalisée dans le functions.php du thème. Déplacez-la dans un plugin indépendant, le thème pourra être changé sereinement et le client pourra le maintenir lui-même sur le long terme.
Une phrase à retenir : ce qui se règle en 20 lignes de hook ne mérite pas une extension tierce de 5000 lignes. Moins de code, moins de dérapages possibles.
