
📤 Invio di moduli in wordpress headless: API REST per Contact Form 7 e Gravity Forms
Stai costruendo un sito su WordPress e hai già sistemato il modulo di contatto. Plugin come Contact Form 7 ti forniscono HTML già pronto, validazione, salvataggio degli invii e decine di integrazioni. Fai clic su «Installa», incolli lo shortcode e in due minuti hai finito.
Ma tutto cambia quando WordPress diventa un CMS headless. Sei responsabile dell'intero frontend: React, Vue, HTML/JS puro. E il plugin per i moduli che prima generava il markup per te non controlla più il lato client. La sua API REST, tuttavia, è ancora lì. Basta inviare una POST all'endpoint corretto e tutta la potenza del plugin (validazione dei campi, salvataggio, integrazioni) resta a tua disposizione.
Nella pratica, puoi coprire anche casi puramente «tradizionali» tramite l'API REST dei plugin per moduli. Mettiamo che tu stia sviluppando un tema personalizzato con Tailwind e il markup fisso di CF7 con la sua rigida struttura di classi stoni. Inviare i dati via API ti permette di controllare ogni pixel del modulo senza abbandonare l'ecosistema consolidato del plugin.
💡 Panoramica rapida:
- Quali endpoint mettono a disposizione Contact Form 7 e Gravity Forms e come attivarli in un ambiente headless.
- Quale formato usare per inviare i campi affinché il plugin accetti correttamente i dati e restituisca un risultato.
- Come costruire un modulo HTML, collegare una richiesta fetch e mostrare all'utente un messaggio di successo o gli errori di validazione.
- Come unificare i diversi formati di risposta di CF7 e Gravity Forms in un'unica comoda struttura.
Cosa devi sapere sugli endpoint
Inviare dati tramite l'API REST è la parte tecnicamente semplice. Entrambi i plugin si aspettano una POST a un endpoint in cui il segmento dinamico dell'URL è l'identificativo del modulo specifico.
Contact Form 7 mette a disposizione un'API REST subito dopo l'attivazione. L'endpoint ha questo aspetto:
1 https://your-site.tld/wp-json/contact-form-7/v1/contact-forms/<FORM_ID>/feedback
A partire dalla versione 5.8 (agosto 2023), Contact Form 7 è passato a identificativi di modulo con hash SHA-1. I vecchi ID numerici funzionano ancora, ma per i nuovi moduli devi recuperare l'identificativo dall'URL della pagina di modifica del modulo nel pannello di amministrazione (l'ultimo segmento dopo post=). Ad aprile 2026, il plugin ha oltre 10 milioni di installazioni attive ed è testato fino a WordPress 7.0.
Gravity Forms utilizza l'API REST v2 (disponibile dalla versione 2.4):
1 https://your-site.tld/wp-json/gf/v2/forms/<FORM_ID>/submissions
Nota importante: l'API REST di Gravity Forms è disabilitata per impostazione predefinita. Per attivarla, vai nelle impostazioni del plugin → scheda REST API → seleziona «Abilita l'accesso all'API». Per l'endpoint di invio del modulo non è richiesta una chiave API: è pubblico per progettazione. L'identificativo del modulo in Gravity Forms è numerico e visibile nel pannello di amministrazione durante la modifica.
Struttura del corpo della richiesta
Prendiamo un modulo di esempio con cinque campi: testo obbligatorio, email e data (antecedente al 4 ottobre 1957), un'area di testo facoltativa e una casella di controllo obbligatoria.

Contact Form 7 si aspetta chiavi nel formato definito tramite la sintassi dei form tag. La chiave corrisponde all'attributo name del campo 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 adotta un approccio diverso: identificatori incrementali generati automaticamente con il prefisso input_. L'ID del campo è visibile direttamente nel pannello di amministrazione quando si modifica un campo specifico.

Per lo stesso modulo, il corpo della richiesta per Gravity Forms ha un aspetto diverso:
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 }
Concetto chiave: se assegni agli input HTML attributi name che corrispondono alle chiavi attese dal plugin, la mappatura avviene in automatico e FormData raccoglie i dati nel formato corretto senza bisogno di mappature manuali.
Costruire l'HTML e inviare la richiesta
Per Contact Form 7, il markup HTML si presenta così (nota che action è l'endpoint e gli attributi name dei campi corrispondono alle chiavi viste sopra):
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>
Per Gravity Forms cambiano solo action e gli attributi name:
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>
Ora l'invio tramite JavaScript: FormData raccoglie i valori tramite name in automatico, quindi non serve alcuna mappatura:
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);
I dati vengono inviati. Ma questo non basta per l'utente, che ha bisogno di un feedback: un messaggio di successo, l'evidenziazione dei campi con errori, una notifica globale. Per fortuna entrambi i plugin restituiscono queste informazioni nella risposta.
Validazione: il server decide, il client mostra
Oltre alla validazione HTML5 nativa (attributi come required, type="email" e max), conviene affidarsi al controllo delle regole lato server offerto dai plugin. Il motivo: le regole sono configurate centralmente nell'amministrazione di WordPress e duplicarle sul client significa doppio lavoro e una fonte di incongruenze.
Sia Contact Form 7 che Gravity Forms restituiscono gli errori di validazione direttamente nel corpo della risposta. Per scenari complessi (campi condizionali, validazione dipendente), basarsi sulla validazione lato server è particolarmente vantaggioso: non devi sincronizzare la logica tra frontend e impostazioni del plugin.
Il compito si riduce a tre passaggi: fare il parsing della risposta JSON, estrarre i messaggi di errore e inserirli nel DOM accanto ai campi corrispondenti.
Formati di risposta e normalizzazione
Risposta di Contact Form 7 in caso di errore di validazione:
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 }
In caso di successo, la risposta è più compatta:
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 risposta di Gravity Forms in caso di errore di validazione ha una struttura diversa:
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 }
E una risposta di successo contiene la conferma all'interno di 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 differenza di approccio è evidente: CF7 incorpora gli errori in un array di oggetti con selettori CSS, mentre Gravity Forms usa un oggetto piatto con chiavi numeriche senza il prefisso input_. Il messaggio di successo di Gravity Forms arriva racchiuso in HTML. Le chiavi dei campi nelle risposte di CF7 sono incorporate nei selettori (ad esempio, span.wpcf7-form-control-wrap.somebodys-name) e richiedono estrazione tramite regex.
Invece di creare una logica ramificata per ciascun plugin, è più comodo normalizzare entrambe le risposte in un formato unificato:
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 }
In caso di successo, isSuccess viene impostato su true e validationError è un oggetto vuoto.
Codice di normalizzazione per 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 };
Codice di normalizzazione per Gravity Forms (nota: alle chiavi di errore viene aggiunto il prefisso input_ in modo che corrispondano alle chiavi della richiesta):
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 };
Ora hai un oggetto di risposta unificato, indipendentemente dal plugin. Non resta che scrivere la visualizzazione degli errori e l'attivazione/disattivazione delle classi sugli elementi del DOM, e il form è pronto all'uso.
Dalla normalizzazione a un'interfaccia reattiva
Una volta convertita la risposta in una struttura unificata, mostrare il feedback si riduce a manipolazione del DOM. Aggiungere un messaggio di errore accanto a un campo, attivare una classe su un wrapper e mostrare una notifica globale: queste tre azioni sono sufficienti per la stragrande maggioranza degli scenari.
Per aggiornamenti reattivi dell'interfaccia, librerie dichiarative leggere come Alpine.js sono molto comode. Sintassi minimale, nessun passaggio di build e integrazione naturale con le risposte del server la rendono una scelta pratica per i form in un ambiente headless. L'approccio con Alpine.js è stato trattato in dettaglio su CSS-Tricks; il codice di quel materiale funziona pressoché invariato con la risposta normalizzata che abbiamo ottenuto sopra.
In conclusione
Replicare la funzionalità lato client che i plugin per form offrono «out of the box» richiede un paio d'ore di lavoro per form semplici. Un bel vantaggio: astraendo la risposta tramite una funzione normalizer, si ottiene un backend intercambiabile. Passare da Contact Form 7 a Gravity Forms (o viceversa) si può fare senza modifiche al frontend; basta sostituire l'endpoint e la funzione normalizer.
Form multi-pagina, anteprime di immagini caricate, calcolatori di prezzo: sì, quello è sviluppo impegnativo. Ma più i requisiti di un progetto sono unici, più è forte la motivazione per un frontend personalizzato sopra la REST API: non si lotta contro il markup di qualcun altro né si aggirano i limiti di un rendering predefinito.
L'approccio headless ai form non è un futuro ipotetico. Oggi plugin come Contact Form 7 e Gravity Forms offrono REST API complete, e i framework frontend permettono di costruire un form in ore anziché in giorni. Provatelo sul prossimo progetto in cui l'aspetto del form è critico: usate CF7 o GF come backend e costruite l'interfaccia da zero. Probabilmente resterete sorpresi da quanto sia lineare.
⁉️🤔 Domande frequenti
La REST API di Contact Form 7 funziona con la versione gratuita?
Sì, la REST API è disponibile subito dopo aver attivato il plugin gratuito; non serve alcuna configurazione aggiuntiva. Ad aprile 2026, Contact Form 7 ha oltre 10 milioni di installazioni attive e la REST API è una parte stabile del core del plugin dalla versione 4.8.
Cosa cambia con gli ID form hash nelle versioni più recenti di Contact Form 7?
A partire dalla versione 5.8 (agosto 2023), CF7 genera un hash SHA-1 come identificatore del form invece di un ID numerico. I vecchi ID numerici funzionano ancora. L'hash si trova nell'URL della pagina di modifica del form nel pannello di amministrazione:
/wp-admin/admin.php?page=wpcf7&post=<HASH>&action=edit. Si inserisce nell'endpoint allo stesso modo di un ID numerico.
Serve una chiave API per inviare form tramite la REST API di Gravity Forms?
No, l'endpoint
/gf/v2/forms/<ID>/submissionsnon richiede autenticazione per l'invio. Tuttavia, la REST API di Gravity Forms è disabilitata di default; bisogna attivarla nelle impostazioni del plugin (Form → Impostazioni → REST API → Abilita accesso all'API).
Si può usare lo stesso codice JavaScript sia per Contact Form 7 che per Gravity Forms?
Sì, è esattamente lo scopo della normalizzazione della risposta. Entrambe le funzioni normalizer (per CF7 e GF) restituiscono un oggetto con la stessa struttura: campi
isSuccess,messageevalidationError. Si collega la funzione appropriata in base al plugin e tutto il resto del codice (visualizzazione errori, evidenziazione campi, notifica globale) funziona senza modifiche.
Cosa fare se il form non si invia e il server restituisce un 404?
Verificate tre cose: se la REST API del plugin è abilitata (particolarmente rilevante per Gravity Forms), se l'identificativo del form nell'URL dell'endpoint è corretto e se la REST API viene bloccata a livello di server o da un plugin di sicurezza. Per Contact Form 7, assicuratevi anche che la REST API di WordPress sia globalmente attiva; senza di essa, CF7 non può elaborare gli invii AJAX.



