Créer un plugin WordPress est la manière la plus directe d’étendre les fonctionnalités d’un site sans toucher au code du CMS lui-même. Un plugin bien structuré survit aux mises à jour WordPress, peut être réutilisé sur plusieurs sites, et si la qualité est au rendez-vous, peut être distribué sur le répertoire officiel WordPress.org. En 2026, avec WordPress 6.5 et la montée en puissance de l’API Blocks Gutenberg, la maîtrise du développement de plugins est un atout différenciant pour tout développeur web travaillant dans l’écosystème WordPress.
Structure de fichiers et en-tête du plugin
Un plugin WordPress minimal se compose d’un seul fichier PHP placé dans /wp-content/plugins/mon-plugin/mon-plugin.php. L’en-tête du fichier principal (le plugin header) est un bloc de commentaires PHP standardisé que WordPress parse pour afficher le nom, la version, la description et l’auteur dans l’interface d’administration. Sans cet en-tête, WordPress ne reconnaît pas le fichier comme un plugin.
Pour les plugins plus complexes, la convention est d’organiser le code en sous-dossiers : includes/ pour les classes PHP, admin/ pour les fichiers spécifiques au back-office, public/ pour le frontend, languages/ pour les fichiers de traduction .pot/.po/.mo, et assets/ pour CSS et JS. Cette structure est suivie par la majorité des plugins du répertoire officiel et par le WordPress Plugin Boilerplate, le starter template de référence.
Le fichier principal du plugin doit impérativement vérifier l’accès direct pour des raisons de sécurité. La constante ABSPATH est définie par WordPress au chargement — si elle n’est pas définie, c’est qu’on accède au fichier directement via HTTP, et il faut exit immédiatement. Cette vérification doit être présente dans chaque fichier PHP de votre plugin, pas seulement le fichier principal.
Actions et filtres : le système de hooks WordPress
Le système de hooks WordPress est le mécanisme fondamental qui permet aux plugins d’interagir avec le CMS sans modifier ses fichiers. Il existe deux types de hooks : les actions (add_action) qui exécutent du code à des moments précis du cycle de vie WordPress (init, wp_enqueue_scripts, save_post…), et les filtres (add_filter) qui permettent de modifier des données avant qu’elles soient utilisées ou affichées.
La priorité d’un hook (troisième paramètre d’add_action/add_filter, défaut 10) détermine l’ordre d’exécution par rapport aux autres fonctions accrochées au même hook. Une priorité plus faible s’exécute en premier. Si vous voulez modifier une valeur après que Rank Math ou WooCommerce l’ait elle-même modifiée, utilisez une priorité > 20. Si vous voulez agir avant WordPress core, utilisez une priorité < 10.
Pour les plugins orientés objet (recommandé dès que le plugin dépasse 200 lignes), les hooks s’enregistrent dans une méthode init() ou dans le constructeur de la classe principale. La syntaxe est add_action(‘hook_name’, array($this, ‘method_name’)). Évitez les fonctions standalone globales dans les plugins OO — elles polluent le namespace global et risquent des conflits avec d’autres plugins.
Options, métadonnées et la Settings API
WordPress propose trois mécanismes de stockage pour les plugins. Les Options (options API : get_option, update_option, delete_option) stockent dans la table wp_options des valeurs globales au site. Les Post Meta (add_post_meta, get_post_meta) stockent des métadonnées attachées à un post spécifique. Les User Meta (add_user_meta, get_user_meta) stockent des données par utilisateur.
Pour créer une page de configuration dans l’admin WordPress, utilisez la Settings API. Elle est plus verboseuse que d’écrire un formulaire HTML à la main, mais elle gère automatiquement la sécurité (nonce CSRF, capabilities check), l’affichage cohérent avec le style admin WordPress, et l’enregistrement des options. Les fonctions clés : register_setting() pour déclarer les options, add_settings_section() pour les groupes de champs, add_settings_field() pour les champs individuels.
Un piège courant est de stocker des données sensibles (clés API, passwords) en clair dans wp_options. Préférez les constantes définies dans wp-config.php pour les secrets — elles ne sont pas accessibles depuis l’interface admin (donc moins exposées aux attaques XSS) et sont exclues des sauvegardes de base de données standards. Votre plugin peut vérifier si une constante est définie avant de proposer le champ de formulaire correspondant.
Code complet d’un plugin de base avec shortcode et admin
Voici la structure complète d’un plugin minimal mais fonctionnel avec un shortcode, une page admin et une option :
<?php
/**
* Plugin Name: Mon Premier Plugin
* Plugin URI: https://exemple.com/mon-plugin
* Description: Un plugin de démonstration avec shortcode et page admin.
* Version: 1.0.0
* Author: Votre Nom
* Text Domain: mon-plugin
* Requires at least: 6.0
* Tested up to: 6.5
*/
if (!defined('ABSPATH')) exit;
define('MON_PLUGIN_VERSION', '1.0.0');
define('MON_PLUGIN_DIR', plugin_dir_path(__FILE__));
class MonPlugin {
public function __construct() {
add_action('init', array($this, 'init'));
add_action('admin_menu', array($this, 'admin_menu'));
add_shortcode('mon_shortcode', array($this, 'shortcode_handler'));
}
public function init() {
load_plugin_textdomain('mon-plugin', false,
dirname(plugin_basename(__FILE__)) . '/languages/');
}
public function admin_menu() {
add_options_page(
__('Mon Plugin Settings', 'mon-plugin'),
__('Mon Plugin', 'mon-plugin'),
'manage_options',
'mon-plugin',
array($this, 'admin_page')
);
}
public function admin_page() {
if (!current_user_can('manage_options')) return;
if (isset($_POST['mon_plugin_save'])) {
check_admin_referer('mon_plugin_action', 'mon_plugin_nonce');
update_option('mon_plugin_message',
sanitize_text_field($_POST['mon_plugin_message']));
echo '<div class="notice notice-success"><p>' .
esc_html__('Paramètres sauvegardés.', 'mon-plugin') . '</p></div>';
}
$message = get_option('mon_plugin_message', 'Bonjour le monde !');
?>
<div class="wrap">
<h1><?php echo esc_html(get_admin_page_title()); ?></h1>
<form method="post">
<?php wp_nonce_field('mon_plugin_action', 'mon_plugin_nonce'); ?>
<label for="mon_plugin_message">
<?php _e('Message à afficher', 'mon-plugin'); ?>
</label>
<input type="text" id="mon_plugin_message"
name="mon_plugin_message"
value="<?php echo esc_attr($message); ?>"
class="regular-text">
<?php submit_button(__('Sauvegarder', 'mon-plugin'),
'primary', 'mon_plugin_save'); ?>
</form>
</div>
<?php
}
public function shortcode_handler($atts) {
$atts = shortcode_atts(array('class' => ''), $atts, 'mon_shortcode');
$message = get_option('mon_plugin_message', 'Bonjour le monde !');
$class = sanitize_html_class($atts['class']);
return '<div class="mon-plugin-message ' . esc_attr($class) . '">'
. esc_html($message) . '</div>';
}
}
new MonPlugin();
Sécurité : les vecteurs d’attaque spécifiques aux plugins
Les plugins WordPress sont la première source de vulnérabilités dans l’écosystème. Les quatre vecteurs principaux sont : les injections SQL (évitables avec $wpdb->prepare()), les failles XSS (évitables avec esc_html, esc_attr, esc_url avant toute sortie), les CSRF (évitables avec wp_nonce_field + check_admin_referer), et les problèmes de contrôle d’accès (évitables avec current_user_can() avant chaque action sensible).
Pour les requêtes en base de données, n’utilisez jamais l’interpolation de variables directement dans les requêtes SQL. La méthode $wpdb->prepare() est obligatoire dès qu’une valeur dynamique entre dans une requête. Elle utilise un système de placeholders (%s pour les strings, %d pour les entiers, %f pour les floats) similaire à sprintf et protège automatiquement contre les injections.
La validation et la sanitisation des données sont deux étapes distinctes qui ont lieu à des moments différents. La validation (is_email(), intval(), preg_match()) vérifie que la donnée correspond au format attendu — si non, rejetez-la. La sanitisation (sanitize_text_field(), wp_kses(), absint()) nettoie la donnée pour la rendre sûre à stocker ou à afficher. Validez à l’entrée (formulaires, API), sanitisez avant le stockage, échappez (escape) à la sortie.
Internationalisation (i18n) et préparation à la traduction
Un plugin bien construit est prêt pour la traduction dès le départ, même si vous n’avez pas l’intention de le traduire immédiatement. WordPress utilise gettext pour l’internationalisation. La règle fondamentale : ne jamais utiliser de chaîne de texte directement dans le HTML — enveloppez-les toujours dans __() pour les chaînes retournées, _e() pour les chaînes affichées directement, et printf() + __() pour les chaînes avec des variables.
Le text domain du plugin (défini dans l’en-tête du fichier principal) doit correspondre exactement à celui utilisé dans vos fonctions __() et _e(). Pour générer le fichier .pot (template de traduction), utilisez WP-CLI : wp i18n make-pot . languages/mon-plugin.pot. Ce fichier est uploadé sur WordPress.org Translate si vous publiez votre plugin, ou distribué avec votre plugin pour les traductions personnalisées.
Chargez les fichiers de traduction dans le hook init (ou plugins_loaded) avec load_plugin_textdomain(). Depuis WordPress 6.1, les plugins du répertoire officiel bénéficient de la Just-In-Time (JIT) translation loading — les fichiers de traduction ne sont chargés qu’à la première utilisation du text domain, réduisant la consommation mémoire sur les sites multilingues avec de nombreux plugins.
Publication sur WordPress.org et bonnes pratiques finales
Le répertoire WordPress.org est la vitrine pour 60 000+ plugins avec des millions d’installations hebdomadaires. Pour soumettre votre plugin, il doit passer une revue manuelle par l’équipe WordPress.org qui vérifie la sécurité, le respect des guidelines (pas de code obfusqué, pas de tracking utilisateur sans consentement, pas de dépendances externes non déclarées), et la compatibilité avec les dernières versions WordPress.
Avant de soumettre, utilisez le Plugin Check (PCP) plugin officiel ou le CLI wp plugin check pour détecter automatiquement les violations courantes. Testez votre plugin avec WordPress Beta Tester en mode Bleeding Edge pour détecter les incompatibilités avec les versions futures. Assurez-vous que votre plugin.php inclut une entête Requires at least et Tested up to à jour.
Une fois publié, la maintenance est continue. Les failles de sécurité découvertes dans votre plugin sont communiquées via le programme de disclosure coordonné de WordPress.org. Vous recevez un email privé avec les détails, un délai pour patcher (généralement 7 jours), puis votre plugin est désactivé automatiquement sur tous les sites s’il n’est pas corrigé dans les délais. La réactivité est donc critique pour maintenir la réputation de votre plugin.
Blocs Gutenberg : l’API Blocks pour étendre l’éditeur
Depuis WordPress 5.0, l’éditeur Gutenberg est le standard pour la création de contenu. En 2026, avec WordPress 6.5 et le Full Site Editing mature, créer des blocs personnalisés est devenu une compétence essentielle pour les développeurs de plugins. Un bloc Gutenberg personnalisé permet d’offrir aux rédacteurs une interface intuitive dans l’éditeur visuel plutôt qu’un shortcode opaque. La différence d’expérience utilisateur est significative.
Un bloc Gutenberg se compose de deux parties : le code PHP (côté serveur) pour l’enregistrement et le rendu éventuel, et le code JavaScript/React (côté éditeur) pour l’interface de configuration dans l’éditeur. La commande @wordpress/create-block génère automatiquement la structure complète d’un bloc avec un package.json, un webpack.config.js et les fichiers JS et PHP pré-configurés. C’est le point de départ recommandé — ne tentez pas de configurer l’environnement manuellement.
Pour les blocs dynamiques (dont le rendu dépend de données PHP, comme une liste d’articles récents), la méthode render_callback dans register_block_type() permet d’exécuter du PHP au moment de l’affichage plutôt que de sérialiser le HTML dans la base de données. Cela garantit que le contenu reste frais lors des mises à jour de données, mais implique que la page ne peut pas être entièrement mise en cache statiquement si elle contient ce bloc. Le choix entre bloc statique et bloc dynamique doit être fait en fonction des besoins réels de mise à jour du contenu.
Tests automatisés pour plugins WordPress
Un plugin sérieux dispose de tests automatisés. WordPress Core utilise PHPUnit depuis des années, et l’écosystème plugins a suivi. WP-CLI permet de créer le scaffold de tests en une commande : wp scaffold plugin-tests mon-plugin configure automatiquement PHPUnit avec les fixtures WordPress, créant un environnement de test isolé avec une base de données dédiée. Les tests d’intégration peuvent appeler les fonctions WordPress réelles (add_option, wp_insert_post) dans un environnement contrôlé.
Pour les tests end-to-end qui vérifient le comportement dans un vrai navigateur, WP-Env (l’environnement Docker officiel de WordPress) combiné avec Playwright ou Cypress est devenu le standard. WP-Env lance un WordPress complet avec votre plugin activé en une commande (wp-env start), sur lequel vous exécutez vos tests E2E. Cette configuration est reproducible en CI (GitHub Actions) et garantit que votre plugin fonctionne réellement dans un WordPress fresh install.
La couverture de tests minimale recommandée pour un plugin soumis au répertoire WordPress.org : des tests unitaires pour les fonctions de validation/sanitisation (les bugs de sécurité les plus courants), des tests d’intégration pour les hooks critiques (save_post, user_register, woocommerce_checkout_order_processed selon votre plugin), et au moins un test E2E vérifiant le workflow utilisateur principal. C’est la garantie que les futures mises à jour WordPress ne casseront pas silencieusement votre plugin.
Commentaires (0)
Laisser un commentaire
Les commentaires sont modérés. Questions WordPress, cybersécurité ou dev web bienvenues.