Tutoriel : créer un plugin WordPress de génération automatique de méta-descriptions et résumés par IA
Rédiger une méta-description unique et pertinente pour chacun de vos articles WordPress est un pilier du SEO technique. Mais quand votre site compte 500, 2 000 ou 10 000 articles, le faire manuellement relève de l’impossible. Dans ce tutoriel pas à pas, nous allons construire un plugin WordPress sur mesure — WP AI Meta Generator — qui utilise l’API Claude pour générer automatiquement des méta-descriptions et des résumés d’articles, avec une interface d’administration complète. Prêt à coder ? C’est parti.
Prérequis et architecture du plugin
Ce dont vous avez besoin
- WordPress 6.5 ou supérieur
- PHP 8.1 ou supérieur
- Une clé API Anthropic (compte Claude)
- Accès FTP ou SSH à votre installation WordPress
Fonctionnalités du plugin
Notre plugin va : générer une méta-description SEO (150-160 caractères) pour tout article qui n’en a pas, produire un résumé de 2-3 phrases utilisable comme extrait, traiter les articles en masse via un bouton dans l’admin, s’intégrer proprement aux hooks WordPress, et stocker les résultats dans les champs natifs de WordPress — sans table custom, sans dépendance externe lourde.
Structure des fichiers
wp-ai-meta-generator/
├── wp-ai-meta-generator.php ← Fichier principal du plugin
├── includes/
│ ├── class-admin.php ← Interface d'administration
│ ├── class-generator.php ← Logique d'appel API Claude
│ └── class-bulk-process.php ← Traitement par lots
├── assets/
│ ├── css/admin.css
│ └── js/admin.js
└── readme.txt
Étape 1 : le fichier principal du plugin
Créez le dossier wp-ai-meta-generator dans /wp-content/plugins/ et commençons par le fichier principal.
<?php
/**
* Plugin Name: WP AI Meta Generator
* Description: Génère automatiquement des méta-descriptions et résumés via l'API Claude
* Version: 1.0.0
* Author: Gabriel Tamboise
* Text Domain: wp-ai-meta
* Requires PHP: 8.1
*/
defined('ABSPATH') || exit;
define('WPAIMG_VERSION', '1.0.0');
define('WPAIMG_PATH', plugin_dir_path(__FILE__));
define('WPAIMG_URL', plugin_dir_url(__FILE__));
// Chargement des classes principales
require_once WPAIMG_PATH . 'includes/class-generator.php';
require_once WPAIMG_PATH . 'includes/class-bulk-process.php';
require_once WPAIMG_PATH . 'includes/class-admin.php';
// Initialisation
add_action('plugins_loaded', function () {
WPAIMG\Admin\init();
});
// Hook : génération automatique à la publication
add_action('save_post', function (int $post_id, \WP_Post $post, bool $update) {
if (wp_is_post_autosave($post_id) || wp_is_post_revision($post_id)) {
return;
}
if ($post->post_type !== 'post') {
return;
}
$generator = new WPAIMG\Generator();
// Générer la méta-description si absente du champ dédié
$existing_meta = get_post_meta($post_id, '_wpa_img_meta_description', true);
if (empty($existing_meta)) {
$meta_desc = $generator->generate_meta_description($post);
if ($meta_desc) {
update_post_meta($post_id, '_wpa_img_meta_description', $meta_desc);
}
}
// Générer un résumé si l'extrait est vide
if (empty($post->post_excerpt)) {
$summary = $generator->generate_summary($post);
if ($summary) {
wp_update_post([
'ID' => $post_id,
'post_excerpt' => $summary,
]);
}
}
}, 20, 3);
Rien de magique ici : on définit les constantes, on charge nos classes, et on branche un hook save_post qui déclenche la génération uniquement pour les articles publiés qui n’ont pas déjà des données IA. Le paramètre 20 de priorité garantit que le hook s’exécute après les sauvegardes standards de WordPress.
Étape 2 : la classe Generator — le cœur du plugin
Cette classe encapsule la communication avec l’API Claude. On utilise wp_remote_post() plutôt que cURL pour rester dans l’écosystème WordPress et bénéficier du système de cache HTTP natif.
<?php
// includes/class-generator.php
namespace WPAIMG;
defined('ABSPATH') || exit;
class Generator
{
private string $api_key;
private string $endpoint = 'https://api.anthropic.com/v1/messages';
private string $model = 'claude-sonnet-4-20250514';
private int $max_tokens = 300;
public function __construct()
{
$this->api_key = get_option('wpa_img_anthropic_key', '');
}
/**
* Nettoie le contenu HTML pour l'envoyer à l'API.
*/
private function prepare_content(\WP_Post $post): string
{
$content = wp_strip_all_tags($post->post_content);
$content = html_entity_decode($content, ENT_QUOTES | ENT_HTML5, 'UTF-8');
// Limiter à 3000 caractères pour maîtriser le coût en tokens
return mb_substr($content, 0, 3000);
}
/**
* Appel unifié à l'API Claude.
*/
private function call_claude(string $system_prompt, string $user_message): ?string
{
if (empty($this->api_key)) {
error_log('WP AI Meta Generator : clé API Anthropic non configurée.');
return null;
}
$body = [
'model' => $this->model,
'max_tokens' => $this->max_tokens,
'system' => $system_prompt,
'messages' => [
['role' => 'user', 'content' => $user_message],
],
];
$response = wp_remote_post($this->endpoint, [
'timeout' => 30,
'headers' => [
'x-api-key' => $this->api_key,
'anthropic-version' => '2023-06-01',
'Content-Type' => 'application/json',
],
'body' => wp_json_encode($body),
]);
if (is_wp_error($response)) {
error_log('WP AI Meta Generator : erreur HTTP - ' . $response->get_error_message());
return null;
}
$data = json_decode(wp_remote_retrieve_body($response), true);
return $data['content'][0]['text'] ?? null;
}
/**
* Génère une méta-description SEO optimisée (150-160 caractères).
*/
public function generate_meta_description(\WP_Post $post): ?string
{
$system = <<<EOT
Tu es un rédacteur SEO expert. À partir du contenu d'un article, tu génères une
méta-description de 150 à 160 caractères maximum. Règles :
- Inclure le mot-clé principal naturellement.
- Utiliser un ton engageant et informatif.
- Ne pas dépasser 160 caractères.
- Retourner UNIQUEMENT la méta-description, sans guillemets ni commentaire.
EOT;
$user = sprintf(
"Titre : %s\n\nContenu : %s",
$post->post_title,
$this->prepare_content($post)
);
$result = $this->call_claude($system, $user);
return $result ? trim($result) : null;
}
/**
* Génère un résumé de 2-3 phrases.
*/
public function generate_summary(\WP_Post $post): ?string
{
$system = <<<EOT
Tu es un assistant éditorial. Résume l'article fourni en 2 ou 3 phrases
maximum, en français. Le résumé doit donner envie de lire l'article complet.
Retourne UNIQUEMENT le résumé, sans commentaire.
EOT;
$user = sprintf(
"Titre : %s\n\nContenu : %s",
$post->post_title,
$this->prepare_content($post)
);
$result = $this->call_claude($system, $user);
return $result ? trim($result) : null;
}
}
Points importants : on utilise Claude Sonnet 4 (claude-sonnet-4-20250514) pour son excellent rapport qualité/coût sur les tâches de synthèse. Le contenu est limité à 3 000 caractères pour contrôler la consommation de tokens — une méta-description ne nécessite pas l’article entier pour être pertinente. Enfin, toutes les erreurs sont loguées silencieusement : pas de message d’erreur visible pour le visiteur du site.
Étape 3 : l’interface d’administration
On crée maintenant la page de réglages et le bouton de traitement par lots dans l’admin WordPress.
<?php
// includes/class-admin.php
namespace WPAIMG;
defined('ABSPATH') || exit;
class Admin
{
public static function init(): void
{
add_action('admin_menu', [self::class, 'add_menu']);
add_action('admin_init', [self::class, 'register_settings']);
add_action('admin_enqueue_scripts', [self::class, 'enqueue_assets']);
add_action('wp_ajax_wpa_img_bulk_generate', [self::class, 'handle_bulk_ajax']);
}
public static function add_menu(): void
{
add_options_page(
'WP AI Meta Generator',
'AI Meta Generator',
'manage_options',
'wp-ai-meta',
[self::class, 'render_page']
);
}
public static function register_settings(): void
{
register_setting('wpa_img_settings', 'wpa_img_anthropic_key', [
'type' => 'string',
'sanitize_callback' => 'sanitize_text_field',
'default' => '',
]);
register_setting('wpa_img_settings', 'wpa_img_post_types', [
'type' => 'array',
'default' => ['post'],
]);
}
public static function render_page(): void
{
$key = get_option('wpa_img_anthropic_key', '');
$mask = !empty($key) ? substr($key, 0, 8) . '...' . substr($key, -4) : '';
?>
<div class="wrap">
<h1>🧠 WP AI Meta Generator</h1>
<form method="post" action="options.php">
<?php settings_fields('wpa_img_settings'); ?>
<table class="form-table">
<tr>
<th><label>Clé API Anthropic</label></th>
<td>
<input type="password" name="wpa_img_anthropic_key"
value="<?php echo esc_attr($key); ?>"
class="regular-text" />
<?php if ($mask): ?>
<p class="description">Clé enregistrée : <code><?php echo esc_html($mask); ?></code></p>
<?php endif; ?>
</td>
</tr>
</table>
<?php submit_button('Enregistrer la clé'); ?>
</form>
<hr />
<h2>Traitement par lots</h2>
<p>Génère des méta-descriptions et résumés pour tous les articles publiés
qui n'en possèdent pas encore.</p>
<button id="wpa-img-bulk-btn" class="button button-primary">
🚀 Générer pour tous les articles
</button>
<div id="wpa-img-progress" style="margin-top:12px;"></div>
</div>
<script>
document.getElementById('wpa-img-bulk-btn').addEventListener('click', async function() {
const btn = this;
const prog = document.getElementById('wpa-img-progress');
btn.disabled = true;
btn.textContent = '⏳ Traitement en cours...';
prog.innerHTML = '';
try {
const res = await fetch(ajaxurl, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: 'action=wpa_img_bulk_generate&_ajax_nonce=<?php echo wp_create_nonce("wpa_img_bulk"); ?>'
});
const data = await res.json();
if (data.success) {
prog.innerHTML = `<div class="notice notice-success"><p>
✅ ${data.data.processed} article(s) traités avec succès.
<br>⏱️ ${data.data.duration}s</p></div>`;
} else {
prog.innerHTML = `<div class="notice notice-error"><p>❌ ${data.data.message}</p></div>`;
}
} catch (e) {
prog.innerHTML = `<div class="notice notice-error"><p>❌ Erreur réseau : ${e.message}</p></div>`;
} finally {
btn.disabled = false;
btn.textContent = '🚀 Générer pour tous les articles';
}
});
</script>
<?php
}
public static function handle_bulk_ajax(): void
{
check_ajax_referer('wpa_img_bulk');
if (!current_user_can('manage_options')) {
wp_send_json_error(['message' => 'Permissions insuffisantes.']);
}
$start = microtime(true);
$processed = 0;
$posts = get_posts([
'post_type' => 'post',
'post_status' => 'publish',
'posts_per_page' => 50,
'meta_query' => [
'relation' => 'OR',
['key' => '_wpa_img_meta_description', 'compare' => 'NOT EXISTS'],
['key' => '_wpa_img_meta_description', 'value' => '', 'compare' => '='],
],
]);
$generator = new Generator();
foreach ($posts as $post) {
$meta_desc = $generator->generate_meta_description($post);
if ($meta_desc) {
update_post_meta($post->ID, '_wpa_img_meta_description', $meta_desc);
}
if (empty($post->post_excerpt)) {
$summary = $generator->generate_summary($post);
if ($summary) {
wp_update_post([
'ID' => $post->ID,
'post_excerpt' => $summary,
]);
}
}
$processed++;
usleep(250000); // 250ms délai entre chaque appel (rate limiting)
}
wp_send_json_success([
'processed' => $processed,
'duration' => round(microtime(true) - $start, 1),
]);
}
public static function enqueue_assets(string $hook): void
{
if ($hook !== 'settings_page_wp-ai-meta') {
return;
}
wp_enqueue_style(
'wpa-img-admin',
WPAIMG_URL . 'assets/css/admin.css',
[],
WPAIMG_VERSION
);
}
}
Étape 4 : afficher les méta-descriptions dans le <head>
La méta-description est stockée dans une meta key custom. On doit l’injecter dans le <head> du site, en priorité sur la description automatique de WordPress ou celle d’un plugin SEO.
// Ajouter dans wp-ai-meta-generator.php
add_action('wp_head', function () {
if (!is_singular('post')) {
return;
}
$meta_desc = get_post_meta(get_the_ID(), '_wpa_img_meta_description', true);
if (!empty($meta_desc)) {
printf(
'<meta name="description" content="%s">' . "\n",
esc_attr($meta_desc)
);
}
}, 1);
// Compatibilité Rank Math / Yoast SEO
add_filter('rank_math/frontend/description', function ($description) {
if (is_singular('post')) {
$ai_desc = get_post_meta(get_the_ID(), '_wpa_img_meta_description', true);
if (!empty($ai_desc)) {
return $ai_desc;
}
}
return $description;
});
add_filter('wpseo_metadesc', function ($description) {
if (is_singular('post')) {
$ai_desc = get_post_meta(get_the_ID(), '_wpa_img_meta_description', true);
if (!empty($ai_desc)) {
return $ai_desc;
}
}
return $description;
});
Étape 5 : installation et premier test
- Compressez le dossier
wp-ai-meta-generatoren ZIP ou uploadez-le via FTP dans/wp-content/plugins/. - Activez le plugin dans l’admin WordPress.
- Allez dans Réglages → AI Meta Generator et entrez votre clé API Anthropic.
- Créez un nouvel article de test, publiez-le : le plugin génère automatiquement la méta-description et le résumé.
- Pour traiter les articles existants, utilisez le bouton Générer pour tous les articles.
Optimisations et bonnes pratiques
Mise en cache des résultats
Une fois générées, les méta-descriptions sont stockées en base — on ne rappelle jamais l’API pour un article déjà traité. Pour aller plus loin, vous pouvez ajouter un cache objet via Redis ou Memcached pour éviter les requêtes SQL répétées.
Gestion des erreurs et fallback
// Ajouter dans wp-ai-meta-generator.php
add_filter('wpseo_metadesc', function ($description) {
if (is_singular('post')) {
$ai_desc = get_post_meta(get_the_ID(), '_wpa_img_meta_description', true);
if (!empty($ai_desc)) {
return $ai_desc;
}
}
// Fallback : retourne la description existante ou l'extrait
return $description ?: get_the_excerpt();
}, 20);
Estimation des coûts
Avec Claude Sonnet 4 à 3 $/million de tokens en entrée et 15 $/sortie, une méta-description coûte environ 0,003 $ à générer (3 000 tokens entrée, ~60 tokens sortie). Traiter 1 000 articles revient à environ 3 $. C’est marginal comparé au temps humain économisé.
Conclusion
Vous avez maintenant un plugin WordPress fonctionnel, léger (~200 lignes de code), qui automatise l’une des tâches SEO les plus chronophages. Il s’intègre nativement aux hooks WordPress, respecte les standards de sécurité (nonces, capability checks, sanitization), et joue bien avec les plugins SEO populaires. Prochaine étape : étendre le plugin pour générer aussi des balises alt d’images via l’API Vision de Claude, ou ajouter un mode « re-génération périodique » pour les articles mis à jour. Le code complet est disponible et libre de droit — adaptez-le, améliorez-le, déployez-le !
Commentaires (0)
Laisser un commentaire
Les commentaires sont modérés. Questions WordPress, cybersécurité ou dev web bienvenues.