Construire un bouton « générer avec l’IA » dans l’éditeur de blocs paraît simple sur le papier : un clic, un appel réseau, du texte qui apparaît. La difficulté réelle se situe ailleurs, dans les détails qu’on découvre seulement en le mettant en production — la clé API qu’on a failli exposer côté client, le texte généré qui remplace un paragraphe sans confirmation, ou le bloc qui reste bloqué en chargement si la requête échoue.
Ce tutoriel construit un bloc Gutenberg minimal mais robuste, avec un appel serveur sécurisé et une insertion du texte que le rédacteur peut modifier avant de la garder.
Architecture générale : le bloc ne parle jamais directement au LLM
Le principe de base, non négociable, est que le navigateur du rédacteur n’appelle jamais l’API du fournisseur de LLM directement. Le bloc appelle une route REST WordPress, qui elle-même appelle le service d’IA depuis le serveur, où la clé API reste stockée en toute sécurité, par exemple via une constante définie dans wp-config.php.
Navigateur (bloc Gutenberg)
│ apiFetch( '/wpm/v1/generate' )
▼
Serveur WordPress (route REST)
│ wp_remote_post() avec la clé API
▼
API du fournisseur de LLM
Enregistrer le bloc et son panneau de génération
Le bloc s’enregistre classiquement avec register_block_type() côté PHP et un fichier block.json décrivant ses attributs, dont un champ content pour stocker le texte final.
registerBlockType( 'wpm/generateur-texte', {
edit: ( { attributes, setAttributes } ) => {
const [ isLoading, setIsLoading ] = useState( false );
const generer = async () => {
setIsLoading( true );
const reponse = await apiFetch( {
path: '/wpm/v1/generate',
method: 'POST',
data: { instruction: attributes.instruction },
} );
setAttributes( { content: reponse.texte, brouillon: true } );
setIsLoading( false );
};
return (
<div>
<Button onClick={ generer } isBusy={ isLoading }>
Générer une proposition
</Button>
<RichText
tagName="p"
value={ attributes.content }
onChange={ ( content ) => setAttributes( { content } ) }
/>
</div>
);
},
} );

La route REST côté serveur
Le callback vérifie les permissions, construit le prompt à partir de l’instruction du rédacteur, appelle le modèle et renvoie le texte brut, sans mise en forme imposée : c’est le rédacteur qui décide de l’intégrer tel quel ou de le retravailler.
add_action( 'rest_api_init', function () {
register_rest_route( 'wpm/v1', '/generate', array(
'methods' => 'POST',
'permission_callback' => function () {
return current_user_can( 'edit_posts' );
},
'callback' => function ( WP_REST_Request $request ) {
$instruction = sanitize_textarea_field( $request->get_param( 'instruction' ) );
$reponse = wp_remote_post( 'https://api.openai.com/v1/chat/completions', array(
'timeout' => 25,
'headers' => array(
'Authorization' => 'Bearer ' . WPM_LLM_API_KEY,
'Content-Type' => 'application/json',
),
'body' => wp_json_encode( array(
'model' => 'gpt-4o-mini',
'messages' => array(
array( 'role' => 'user', 'content' => $instruction ),
),
) ),
) );
if ( is_wp_error( $reponse ) ) {
return new WP_Error( 'wpm_llm_error', 'Le service IA est indisponible.' );
}
$corps = json_decode( wp_remote_retrieve_body( $reponse ), true );
$texte = $corps['choices'][0]['message']['content'] ?? '';
return array( 'texte' => wp_kses_post( $texte ) );
},
) );
} );
Le champ « brouillon » : une décision d’interface, pas de code
L’attribut brouillon ajouté au bloc sert à afficher visuellement une bordure orange tant que le rédacteur n’a pas validé le texte. Ce n’est qu’un signal visuel, mais il évite qu’un paragraphe généré automatiquement se retrouve publié sans relecture parce qu’il ressemblait, visuellement, à un paragraphe écrit à la main.
Gérer les échecs sans bloquer l’éditeur
Un appel réseau peut échouer pour de multiples raisons : quota dépassé, timeout, réponse mal formée. Le bloc affiche alors un message d’erreur clair et remet le bouton dans son état initial, plutôt que de laisser l’indicateur de chargement tourner indéfiniment, un défaut que nous avons corrigé après un retour agacé d’un rédacteur un vendredi après-midi.
- Timeout fixé à 25 secondes côté
wp_remote_post(). - Message d’erreur explicite affiché dans le bloc, jamais une page blanche.
- Le contenu déjà présent dans le bloc n’est jamais effacé en cas d’échec de la génération suivante.
Notre verdict
Un bloc de génération de texte réussi tient davantage à ses garde-fous qu’à la qualité du texte produit par le modèle : clé API hors du navigateur, contenu toujours éditable, échecs gérés proprement. Cette base couvre l’essentiel des cas pratiques rencontrés sur les projets clients où ce type de bloc est resté en production plus de quelques semaines.