
📤 Soumission de formulaires dans WordPress headless : API REST pour Contact Form 7 et Gravity Forms
Vous construisez un site sur WordPress et vous avez déjà réglé la question du formulaire de contact. Des extensions comme Contact Form 7 vous fournissent un HTML prêt à l’emploi, la validation, la sauvegarde des soumissions et des dizaines d’intégrations. Vous cliquez sur «Installer», vous collez le shortcode et c’est réglé en deux minutes.
Mais tout change lorsque WordPress devient un CMS headless. Vous êtes responsable de l’intégralité du frontend: React, Vue, HTML/JS pur. Et l’extension de formulaire qui générait le balisage pour vous ne contrôle plus le côté client. Son API REST, en revanche, est toujours là. Il suffit d’envoyer un POST au bon endpoint, et toute la puissance de l’extension (validation des champs, sauvegarde, intégrations) reste à votre disposition.
En pratique, vous pouvez aussi couvrir des cas purement «traditionnels» via l’API REST des extensions de formulaire. Imaginons que vous construisiez un thème sur mesure avec Tailwind et que le balisage figé de CF7, avec sa structure de classes rigide, jure avec le reste. Soumettre le formulaire via l’API vous permet de contrôler chaque pixel du formulaire sans abandonner l’écosystème éprouvé de l’extension.
💡 Aperçu rapide:
- Quels endpoints Contact Form 7 et Gravity Forms mettent à disposition et comment les activer dans un environnement headless.
- Quel format utiliser pour l’envoi des champs afin que l’extension accepte correctement les données et renvoie un résultat.
- Comment construire un formulaire HTML, y attacher une requête fetch et afficher à l’utilisateur un message de succès ou les erreurs de validation.
- Comment unifier les formats de réponse différents de CF7 et Gravity Forms en une structure unique et pratique.
Ce que vous devez savoir sur les endpoints
L’envoi de données via l’API REST est la partie techniquement simple. Les deux extensions attendent un POST vers un endpoint dont le segment d’URL dynamique est l’identifiant du formulaire concerné.
Contact Form 7 fournit une API REST immédiatement après activation. L’endpoint se présente ainsi:
1 https://your-site.tld/wp-json/contact-form-7/v1/contact-forms/<FORM_ID>/feedback
À partir de la version 5.8 (août 2023), Contact Form 7 est passé à des identifiants de formulaire hachés en SHA-1. Les anciens identifiants numériques fonctionnent toujours, mais pour les nouveaux formulaires, vous devez récupérer l’identifiant dans l’URL de la page d’édition du formulaire dans l’administration (le dernier segment après post=). En avril 2026, l’extension compte plus de 10 millions d’installations actives et est testée jusqu’à WordPress 7.0.
Gravity Forms utilise l’API REST v2 (disponible depuis la version 2.4):
1 https://your-site.tld/wp-json/gf/v2/forms/<FORM_ID>/submissions
Remarque importante: l’API REST de Gravity Forms est désactivée par défaut. Pour l’activer, allez dans les réglages de l’extension → onglet API REST → cochez «Activer l’accès à l’API». Aucune clé API n’est requise pour l’endpoint de soumission de formulaire; il est public par conception. L’identifiant de formulaire dans Gravity Forms est numérique et visible dans l’administration lors de l’édition.
Structure du corps de la requête
Prenons un exemple de formulaire avec cinq champs: un champ texte obligatoire, un email et une date (antérieure au 4 octobre 1957), une zone de texte facultative et une case à cocher obligatoire.

Contact Form 7 attend des clés dans le format défini par la syntaxe des balises de formulaire. La clé correspond à l'attribut name du champ concerné dans le HTML:
1 { 2 "somebodys-name": "Marian Kenney", 3 "any-email": "[email protected]", 4 "before-space-age": "1922-03-11", 5 "optional-message": "", 6 "fake-terms": "1" 7 }
Gravity Forms utilise une approche différente: des identifiants incrémentaux générés automatiquement avec le préfixe input_. L'ID du champ est visible directement dans le panneau d'administration lorsque vous éditez un champ spécifique.

Pour le même formulaire, le corps de la requête pour Gravity Forms est différent:
1 { 2 "input_1": "Marian Kenney", 3 "input_2": "[email protected]", 4 "input_3": "1922-03-11", 5 "input_4": "", 6 "input_5_1": "1" 7 }
Point clé à retenir: si vous donnez à vos champs HTML des attributs name qui correspondent aux clés attendues par le plugin, le mapping se fait automatiquement et FormData collecte les données dans le bon format sans mapping manuel.
Construire le HTML et envoyer la requête
Pour Contact Form 7, le balisage HTML ressemble à ceci (notez que action est l'endpoint et que les attributs name des champs correspondent aux clés ci-dessus):
1 <form action="https://your-site.tld/wp-json/contact-form-7/v1/contact-forms/<FORM_ID>/feedback" method="post"> 2 <label for="somebodys-name">Your name</label> 3 <input id="somebodys-name" type="text" name="somebodys-name" required> 4 5 <label for="any-email">Email</label> 6 <input id="any-email" type="email" name="any-email" required> 7 8 <label for="before-space-age">Date</label> 9 <input id="before-space-age" type="date" name="before-space-age" max="1957-10-04" required> 10 11 <label for="optional-message">Message</label> 12 <textarea id="optional-message" name="optional-message"></textarea> 13 14 <label> 15 <input type="checkbox" name="fake-terms" value="1" required> 16 I accept the terms 17 </label> 18 19 <button type="submit">Submit</button> 20 </form>
Pour Gravity Forms, seuls action et les attributs name changent:
1 <form action="https://your-site.tld/wp-json/gf/v2/forms/<FORM_ID>/submissions" method="post"> 2 <label for="input_1">Your name</label> 3 <input id="input_1" type="text" name="input_1" required> 4 <!-- ... --> 5 </form>
Passons maintenant à l'envoi via JavaScript: FormData collecte les valeurs par name automatiquement, aucun mapping n'est donc nécessaire:
1 const formSubmissionHandler = (event) => { 2 event.preventDefault(); 3 4 const formElement = event.target; 5 const { action, method } = formElement; 6 const body = new FormData(formElement); 7 8 fetch(action, { method, body }) 9 .then((response) => response.json()) 10 .then((response) => { 11 if (isFormSubmissionError(response)) { 12 // Handle validation errors 13 handleValidationErrors(response); 14 return; 15 } 16 // Successful submission 17 handleSuccess(response); 18 }) 19 .catch((error) => { 20 // Network error or server unavailable 21 handleNetworkError(error); 22 }); 23 }; 24 25 const formElement = document.querySelector("form"); 26 formElement.addEventListener("submit", formSubmissionHandler);
Les données sont envoyées. Mais cela ne suffit pas pour l'utilisateur: il a besoin d'un retour, un message de succès, la mise en évidence des champs en erreur, une notification globale. Heureusement, les deux plugins renvoient ces informations dans la réponse.
Validation: le serveur décide, le client affiche
Au-delà de la validation HTML5 native (attributs comme required, type="email" et max), il est pertinent de s'appuyer sur les vérifications de règles côté serveur fournies par les plugins. Pourquoi: les règles sont configurées de manière centralisée dans l'administration WordPress et les dupliquer côté client implique un double travail et une source d'incohérences.
Contact Form 7 comme Gravity Forms renvoient les erreurs de validation directement dans le corps de la réponse. Pour les scénarios complexes (champs conditionnels, validation dépendante), s'appuyer sur la validation côté serveur est particulièrement avantageux: vous n'avez pas besoin de synchroniser la logique entre le frontend et les paramètres du plugin.
La tâche se résume à trois étapes: analyser la réponse JSON, extraire les messages d'erreur et les insérer dans le DOM à côté des champs correspondants.
Formats de réponse et normalisation
Réponse de Contact Form 7 en cas d'erreur de validation:
1 { 2 "into": "#", 3 "status": "validation_failed", 4 "message": "One or more fields have an error. Please check and try again.", 5 "posted_data_hash": "", 6 "invalid_fields": [ 7 { 8 "into": "span.wpcf7-form-control-wrap.somebodys-name", 9 "message": "The field is required.", 10 "idref": null, 11 "error_id": "-ve-somebodys-name" 12 } 13 ] 14 }
En cas de succès, la réponse est plus compacte:
1 { 2 "into": "#", 3 "status": "mail_sent", 4 "message": "Thank you for your message. It has been sent.", 5 "posted_data_hash": "d52f9f9de995287195409fe6dcde0c50" 6 }
La réponse de Gravity Forms en cas d’erreur de validation est structurée différemment:
1 { 2 "is_valid": false, 3 "validation_messages": { 4 "1": "This field is required.", 5 "2": "This field is required.", 6 "3": "This field is required.", 7 "5": "This field is required." 8 }, 9 "page_number": 1, 10 "source_page_number": 1 11 }
Et une réponse réussie contient la confirmation dans du HTML:
1 { 2 "is_valid": true, 3 "page_number": 0, 4 "source_page_number": 1, 5 "confirmation_message": "<div>Thanks for contacting us! We will get in touch with you shortly.</div>", 6 "confirmation_type": "message" 7 }
La différence d’approche est évidente: CF7 intègre les erreurs dans un tableau d’objets avec des sélecteurs CSS, tandis que Gravity Forms utilise un objet plat avec des clés numériques sans le préfixe input_. Le message de succès de Gravity Forms est encapsulé dans du HTML. Les clés de champ dans les réponses CF7 sont intégrées dans des sélecteurs (par exemple span.wpcf7-form-control-wrap.somebodys-name) et nécessitent une extraction par expression régulière.
Plutôt que de créer une logique conditionnelle pour chaque plugin, il est plus pratique de normaliser les deux réponses vers un format unifié:
1 { 2 "isSuccess": false, 3 "message": "One or more fields have an error. Please check and try again.", 4 "validationError": { 5 "somebodys-name": "The field is required.", 6 "any-email": "The field is required.", 7 "input_3": "The field is required.", 8 "input_5": "This field is required." 9 } 10 }
En cas de succès, isSuccess est défini sur true et validationError est un objet vide.
Code de normalisation pour Contact Form 7:
1 const normalizeContactForm7Response = (response) => { 2 const isSuccess = response.status === 'mail_sent'; 3 const message = isSuccess 4 ? response.message 5 : response.message || 'One or more fields have an error.'; 6 7 const validationError = isSuccess 8 ? {} 9 : Object.fromEntries( 10 response.invalid_fields.map((error) => { 11 const key = /cf7[-a-z]*.(.*)/.exec(error.into)[1]; 12 return [key, error.message]; 13 }) 14 ); 15 16 return { isSuccess, message, validationError }; 17 };
Code de normalisation pour Gravity Forms (notez que le préfixe input_ est ajouté aux clés d’erreur afin qu’elles correspondent aux clés de la requête):
1 const normalizeGravityFormsResponse = (response) => { 2 const isSuccess = response.is_valid; 3 const message = isSuccess 4 ? stripHtml(response.confirmation_message) 5 : 'There was a problem with your submission.'; 6 7 const validationError = isSuccess 8 ? {} 9 : Object.fromEntries( 10 Object.entries(response.validation_messages).map(([key, value]) => [ 11 `input_${key}`, 12 value, 13 ]) 14 ); 15 16 return { isSuccess, message, validationError }; 17 };
Vous disposez maintenant d’un objet de réponse unifié, quel que soit le plugin. Il ne reste plus qu’à écrire l’affichage des erreurs et le basculement des classes sur les éléments du DOM, et le formulaire est prêt à l’emploi.
De la normalisation à une interface réactive
Une fois la réponse convertie en une structure unifiée, l’affichage du feedback se résume à de la manipulation du DOM. Ajouter un message d’erreur à côté d’un champ, basculer une classe sur un conteneur et afficher une notification globale: ces trois actions suffisent dans la grande majorité des cas.
Pour les mises à jour réactives de l’interface, des bibliothèques déclaratives légères comme Alpine.js sont pratiques. Une syntaxe minimale, aucune étape de build et une intégration naturelle avec les réponses serveur en font un choix pertinent pour les formulaires dans un environnement headless. L’approche Alpine.js a été traitée en détail sur CSS-Tricks; le code de ce support fonctionne quasiment sans modification avec la réponse normalisée que nous avons obtenue ci-dessus.
En résumé
Reproduire les fonctionnalités côté client que les extensions de formulaire fournissent «clés en main» représente quelques heures de travail pour des formulaires simples. Un avantage appréciable: en abstrayant la réponse via une fonction de normalisation, vous obtenez un backend interchangeable. Passer de Contact Form 7 à Gravity Forms (ou l’inverse) peut se faire sans modifier le frontend; il suffit de remplacer l’endpoint et la fonction de normalisation.
Formulaires multi-pages, aperçus d’images téléchargées, calculateurs de prix: là, oui, cela devient du développement conséquent. Mais plus les exigences d’un projet sont spécifiques, plus l’argument en faveur d’un frontend sur mesure adossé à l’API REST se renforce: vous ne luttez pas contre le balisage d’un tiers et vous ne contournez pas les limites d’un rendu préconstruit.
L’approche headless des formulaires n’est pas un futur hypothétique. Aujourd’hui, des extensions comme Contact Form 7 et Gravity Forms proposent des API REST complètes, et les frameworks frontend vous permettent de construire un formulaire en quelques heures plutôt qu’en plusieurs jours. Testez cette approche sur votre prochain projet où l’apparence du formulaire est cruciale: utilisez CF7 ou GF comme backend et construisez l’interface from scratch. Vous serez probablement surpris de voir à quel point c’est simple.
⁉️🤔 Foire aux questions
Est-ce que l’API REST de Contact Form 7 fonctionne avec la version gratuite?
Oui, l’API REST est disponible immédiatement après l’activation de l’extension gratuite; aucune configuration supplémentaire n’est nécessaire. En avril 2026, Contact Form 7 compte plus de 10 millions d’installations actives, et l’API REST fait partie intégrante du cœur de l’extension de manière stable depuis la version 4.8.
Quelle est la différence avec les identifiants de formulaire hashés dans les versions récentes de Contact Form 7?
À partir de la version 5.8 (août 2023), CF7 génère un hash SHA-1 comme identifiant de formulaire au lieu d’un identifiant numérique. Les anciens identifiants numériques fonctionnent toujours. Vous trouverez le hash dans l’URL de la page d’édition du formulaire dans l’administration:
/wp-admin/admin.php?page=wpcf7&post=<HASH>&action=edit. Il s’utilise dans l’endpoint de la même manière qu’un identifiant numérique.
Une clé API est-elle nécessaire pour soumettre des formulaires via l’API REST de Gravity Forms?
Non, l’endpoint
/gf/v2/forms/<ID>/submissionsne nécessite pas d’authentification pour la soumission. En revanche, l’API REST de Gravity Forms est désactivée par défaut; vous devez l’activer dans les paramètres de l’extension (Formulaires → Paramètres → API REST → Activer l’accès à l’API).
Peut-on utiliser le même code JavaScript pour Contact Form 7 et Gravity Forms?
Oui, c’est précisément à cela que sert la normalisation de la réponse. Les deux fonctions de normalisation (pour CF7 et GF) renvoient un objet avec la même structure: les champs
isSuccess,messageetvalidationError. Branchez la fonction appropriée selon l’extension, et tout le reste du code (affichage des erreurs, surlignage des champs, notification globale) fonctionnera sans modification.
Que faire si le formulaire ne s’envoie pas et que le serveur renvoie une erreur 404?
Vérifiez trois choses: si l’API REST de l’extension est activée (particulièrement pertinent pour Gravity Forms), si l’identifiant du formulaire dans l’URL de l’endpoint est correct, et si l’API REST n’est pas bloquée au niveau du serveur ou par une extension de sécurité. Pour Contact Form 7, assurez-vous également que l’API REST de WordPress est globalement active; sans cela, CF7 ne pourra pas traiter les soumissions AJAX.



