Un seul et même formulaire de contact, cliqué douze fois sur douze cartes différentes de la page, et un seul message part réellement. C’est le symptôme remonté par une rédactrice qui gère un annuaire de prestataires affiché via une Query Loop, chaque carte proposant un petit formulaire de contact rapide géré par Contact Form 7.
Visuellement, les douze formulaires sont bien affichés, un par carte. Mais dès qu’un visiteur remplit et soumet l’un d’entre eux, c’est systématiquement le premier de la liste qui reçoit les données envoyées, quelle que soit la carte réellement utilisée.
Symptôme : un seul formulaire actif sur douze affichés
Le pattern de carte utilisé dans la Query Loop contient un bloc core/shortcode avec le shortcode [contact-form-7 id="42" title="Contact rapide"], inséré directement dans le modèle de carte répété pour chaque article de l’annuaire. Le rendu HTML, une fois la page générée, montre bien douze formulaires distincts dans le DOM, mais leurs éléments internes (champs cachés, identifiants de conteneur) partagent des valeurs identiques.
Diagnostic : un identifiant d’unité de formulaire non répété
Contact Form 7 génère normalement un identifiant unique par occurrence de formulaire sur une même page, appelé « unit tag », construit à partir du numéro d’instance et d’un compteur interne. Ce mécanisme fonctionne très bien pour des formulaires insérés manuellement à des endroits distincts d’un article classique. Le problème apparaît ici parce que la Query Loop répète un contenu déjà rendu côté serveur avant que le compteur d’instance de Contact Form 7 n’ait l’occasion de s’incrémenter correctement pour chaque itération : le champ caché _wpcf7_unit_tag se retrouve identique sur plusieurs cartes.

Une vérification rapide dans l’inspecteur du navigateur confirme le diagnostic : les douze formulaires partagent la même valeur pour _wpcf7_unit_tag, ce qui explique pourquoi le script JavaScript de Contact Form 7 (qui s’attache au premier élément trouvé portant cet identifiant) ne réagit qu’au premier formulaire de la page.
Correctif : un shortcode paramétré par identifiant de carte
Plutôt que de tenter de modifier le comportement interne de Contact Form 7, le correctif consiste à forcer une valeur d’identifiant réellement unique par carte, en s’appuyant sur l’identifiant de l’article affiché dans la boucle. La Query Loop expose cet identifiant via un bloc de liaison, réutilisé dans un shortcode enrichi :
[contact-form-7 id="42" title="Contact rapide" html_id="cf7-{{post_id}}"]
Comme les shortcodes Contact Form 7 ne supportent pas nativement ce type de gabarit dynamique, la solution retenue passe par un petit filtre PHP qui intercepte le rendu du bloc shortcode dans le contexte de la Query Loop et injecte l’identifiant de l’article courant :
add_filter( 'render_block_core/shortcode', function ( $contenu, $bloc ) {
if ( str_contains( $contenu, 'contact-form-7' ) && in_the_loop() ) {
$contenu = str_replace(
'html_id=""',
'html_id="cf7-' . get_the_ID() . '"',
$contenu
);
}
return $contenu;
}, 10, 2 );
Ce filtre garantit que chaque instance du formulaire reçoit un attribut id HTML distinct, ce qui suffit à ce que le script de validation de Contact Form 7 cible correctement chaque occurrence indépendamment des autres.
Prévention : tester systématiquement les blocs répétés en boucle
Ce type de conflit ne se limite pas à Contact Form 7. Tout plugin qui génère un identifiant d’instance côté serveur, sans en tenir compte dans un contexte de répétition (Query Loop, pattern synchronisé affiché plusieurs fois sur une même page), peut produire le même symptôme. Quelques réflexes limitent le risque :
- Toujours tester un plugin tiers inséré dans une Query Loop avec au moins trois occurrences visibles simultanément sur la même page.
- Inspecter le DOM généré à la recherche d’identifiants HTML dupliqués, symptôme classique de ce type de conflit.
- Préférer, quand c’est possible, un lien vers un formulaire dédié plutôt qu’un formulaire complet répété dans chaque carte, ce qui limite à la fois le risque de conflit et le poids de la page.
Dès qu’un plugin tiers propose un formulaire à insérer dans une boucle, on vérifie d’abord s’il expose un paramètre d’identifiant personnalisable : c’est souvent le signe qu’il a déjà anticipé ce genre de répétition.
Prévention côté éditorial
Sur ce projet, la décision finale a été de documenter le pattern corrigé dans la bibliothèque de patterns du thème, avec une note explicite à destination des futurs contributeurs : ne jamais dupliquer un shortcode Contact Form 7 brut dans une Query Loop sans passer par le filtre d’identifiant unique. Cette note évite qu’un futur pattern reproduise silencieusement le même bug sur un autre annuaire du site.
En résumé
Le symptôme d’un formulaire qui semble se dupliquer visuellement mais ne fonctionne qu’une seule fois cache presque toujours un conflit d’identifiant généré côté serveur. La correction ne nécessite ni de modifier le plugin ni de renoncer à la répétition en boucle : il suffit de garantir l’unicité de l’identifiant HTML à chaque itération, en s’appuyant sur une donnée déjà disponible dans le contexte de la Query Loop, comme l’identifiant de l’article affiché.