Skip to content

Todo para WordPress, el desarrollo web — y mucho más

📤 Envío de formularios en WordPress headless: API REST para Contact Form 7 y Gravity Forms

📤 Envío de formularios en WordPress headless: API REST para Contact Form 7 y Gravity Forms

Está construyendo un sitio en WordPress y ya tiene resuelto el formulario de contacto. Plugins como Contact Form 7 le entregan HTML listo, validación, almacenamiento de envíos y decenas de integraciones. Hace clic en «Instalar», pega el shortcode y en dos minutos está funcionando.

Pero todo cambia cuando WordPress se convierte en un CMS headless. Usted es responsable de todo el frontend: React, Vue, HTML/JS plano. Y el plugin de formularios que antes generaba el marcado por usted ya no controla el lado del cliente. Su API REST, sin embargo, sigue ahí. Basta con enviar un POST al endpoint correcto y toda la potencia del plugin (validación de campos, almacenamiento, integraciones) queda a su disposición.

En la práctica, también puede cubrir casos puramente «tradicionales» a través de la API REST de los plugins de formularios. Supongamos que está desarrollando un tema personalizado con Tailwind y el marcado fijo de CF7, con su estructura rígida de clases, desentona. Enviar los datos mediante la API le permite controlar cada píxel del formulario sin abandonar el ecosistema consolidado del plugin.

💡 Resumen rápido:

  • Qué endpoints ofrecen Contact Form 7 y Gravity Forms y cómo activarlos en un entorno headless.
  • Qué formato usar al enviar los campos para que el plugin acepte correctamente los datos y devuelva un resultado.
  • Cómo construir un formulario HTML, adjuntar una petición fetch y mostrar al usuario un mensaje de éxito o los errores de validación.
  • Cómo unificar los distintos formatos de respuesta de CF7 y Gravity Forms en una única estructura conveniente.

Lo que necesita saber sobre los endpoints

Enviar datos a través de la API REST es la parte técnicamente sencilla. Ambos plugins esperan un POST a un endpoint donde el segmento dinámico de la URL es el identificador del formulario concreto.

Contact Form 7 proporciona una API REST inmediatamente después de la activación. El endpoint tiene este aspecto:

1https://your-site.tld/wp-json/contact-form-7/v1/contact-forms/<FORM_ID>/feedback

A partir de la versión 5.8 (agosto de 2023), Contact Form 7 pasó a usar identificadores de formulario con hash SHA-1. Los antiguos IDs numéricos siguen funcionando, pero para los formularios nuevos debe obtener el identificador de la URL de la página de edición del formulario en el panel de administración (el último segmento después de post=). A fecha de abril de 2026, el plugin supera los 10 millones de instalaciones activas y está probado hasta WordPress 7.0.

Gravity Forms utiliza la API REST v2 (disponible desde la versión 2.4):

1https://your-site.tld/wp-json/gf/v2/forms/<FORM_ID>/submissions

Nota importante: la API REST de Gravity Forms está desactivada por defecto. Para activarla, vaya a los ajustes del plugin → pestaña API REST → marque «Habilitar el acceso a la API». No se requiere una clave API para el endpoint de envío del formulario; es público por diseño. El identificador del formulario en Gravity Forms es numérico y visible en el panel de administración al editarlo.

Estructura del cuerpo de la petición

Tomemos un formulario de ejemplo con cinco campos: texto obligatorio, correo electrónico y fecha (anterior al 4 de octubre de 1957), un área de texto opcional y una casilla de verificación obligatoria.

Ejemplo de formulario de contacto con cinco campos de entrada

Contact Form 7 espera claves en el formato definido mediante la sintaxis de etiquetas de formulario. La clave coincide con el name del campo correspondiente en 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 utiliza un enfoque distinto: identificadores incrementales generados automáticamente con el prefijo input_. El ID del campo es visible directamente en el panel de administración al editar un campo específico.

Editando un campo de Gravity Forms con el identificador visible input_3

Para el mismo formulario, el cuerpo de la solicitud en Gravity Forms tiene un aspecto diferente:

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}

Conclusión clave: si asigna a sus campos HTML atributos name que coincidan con las claves esperadas por el plugin, la correspondencia se produce automáticamente y FormData recopilará los datos en el formato correcto sin necesidad de mapeo manual.

Construcción del HTML y envío de la solicitud

Para Contact Form 7, el marcado HTML tiene este aspecto (observe que action es el endpoint y los atributos name de los campos coinciden con las claves anteriores):

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>

Para Gravity Forms, solo cambian action y los atributos 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>

Ahora, para el envío mediante JavaScript: FormData recopila los valores por name automáticamente, por lo que no se necesita ningún mapeo:

1const 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
25const formElement = document.querySelector("form");
26formElement.addEventListener("submit", formSubmissionHandler);

Los datos se envían. Pero eso no es suficiente para el usuario, que necesita retroalimentación: un mensaje de éxito, el resaltado de campos con errores, una notificación global. Afortunadamente, ambos plugins devuelven esta información en la respuesta.

Validación: el servidor decide, el cliente muestra

Más allá de la validación HTML5 integrada (atributos como required, type="email" y max), tiene sentido apoyarse en la comprobación de reglas del lado del servidor que proporcionan los plugins. El motivo: las reglas se configuran de forma centralizada en el administrador de WordPress y duplicarlas en el cliente supone trabajo doble y una fuente de inconsistencias.

Tanto Contact Form 7 como Gravity Forms devuelven los errores de validación directamente en el cuerpo de la respuesta. Para escenarios complejos (campos condicionales, validación dependiente), confiar en la validación del servidor es especialmente ventajoso: no necesita sincronizar la lógica entre el frontend y la configuración del plugin.

La tarea se reduce a tres pasos: analizar la respuesta JSON, extraer los mensajes de error e insertarlos en el DOM junto a los campos correspondientes.

Formatos de respuesta y normalización

Respuesta de Contact Form 7 ante un error de validación:

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 caso de éxito, la respuesta es más compacta:

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 respuesta de Gravity Forms ante un error de validación se estructura de manera diferente:

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}

Y una respuesta exitosa contiene la confirmación dentro de 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 diferencia de enfoques es obvia: CF7 incrusta los errores en un arreglo de objetos con selectores CSS, mientras que Gravity Forms utiliza un objeto plano con claves numéricas sin el prefijo input_. El mensaje de éxito de Gravity Forms viene envuelto en HTML. Las claves de campo en las respuestas de CF7 están incrustadas en selectores (por ejemplo, span.wpcf7-form-control-wrap.somebodys-name) y requieren extracción mediante regex.

En lugar de ramificar la lógica para cada plugin, resulta más conveniente normalizar ambas respuestas a un formato unificado:

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 caso de éxito, isSuccess se establece en true y validationError es un objeto vacío.

Código de normalización para Contact Form 7:

1const 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};

Código de normalización para Gravity Forms (nota: a las claves de error se les añade el prefijo input_ para que coincidan con las claves de la solicitud):

1const 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};

Ahora usted dispone de un objeto de respuesta unificado, independientemente del plugin. Solo resta escribir la visualización de errores y la alternancia de clases en los elementos del DOM, y el formulario estará listo para funcionar.

De la normalización a una interfaz activa

Una vez que la respuesta se convierte a una estructura unificada, mostrar la retroalimentación se reduce a manipulación del DOM. Añadir un mensaje de error junto a un campo, alternar una clase en un contenedor y mostrar una notificación global: estas tres acciones son suficientes para la gran mayoría de los escenarios.

Para actualizaciones reactivas de la interfaz, resultan convenientes las librerías declarativas ligeras como Alpine.js. Su sintaxis mínima, la ausencia de un paso de compilación y la integración natural con respuestas del servidor la convierten en una opción práctica para formularios en un entorno headless. El enfoque con Alpine.js se trató en detalle en CSS-Tricks; el código de ese material funciona casi sin cambios con la respuesta normalizada que obtuvimos anteriormente.

En resumidas cuentas

Replicar la funcionalidad del lado del cliente que los plugins de formularios ofrecen «de fábrica» supone un par de horas de trabajo para formularios simples. Una ventaja adicional: al abstraer la respuesta mediante una función normalizadora, obtiene un backend intercambiable. Pasar de Contact Form 7 a Gravity Forms (o viceversa) puede hacerse sin cambios en el frontend; basta con reemplazar el endpoint y la función normalizadora.

Formularios multipágina, vistas previas de imágenes cargadas, calculadoras de precios: sí, eso ya es desarrollo serio. Pero cuanto más particulares son los requisitos de un proyecto, más sentido tiene crear un frontend a medida sobre la API REST: no estará peleándose con el marcado de otro ni sorteando las limitaciones de un renderizado predefinido.

El enfoque headless para formularios no es un futuro hipotético. Hoy en día, plugins como Contact Form 7 y Gravity Forms ofrecen APIs REST completas, y los frameworks de frontend le permiten construir un formulario en horas en lugar de días. Pruébelo en su próximo proyecto donde la apariencia del formulario sea crítica: use CF7 o GF como backend y construya la interfaz desde cero. Es probable que le sorprenda lo sencillo que resulta.

⁉️🤔 Preguntas frecuentes

¿La API REST de Contact Form 7 funciona con la versión gratuita?

Sí, la API REST está disponible inmediatamente después de activar el plugin gratuito; no se requiere configuración adicional. A fecha de abril de 2026, Contact Form 7 cuenta con más de 10 millones de instalaciones activas, y la API REST ha sido una parte estable del núcleo del plugin desde la versión 4.8.

¿Qué cambia con los IDs de formulario en hash en las versiones más recientes de Contact Form 7?

A partir de la versión 5.8 (agosto de 2023), CF7 genera un hash SHA-1 como identificador del formulario en lugar de un ID numérico. Los IDs numéricos antiguos siguen funcionando. Puede encontrar el hash en la URL de la página de edición del formulario en el panel de administración: /wp-admin/admin.php?page=wpcf7&post=<HASH>&action=edit. Se sustituye en el endpoint de la misma manera que un ID numérico.

¿Se requiere una clave API para enviar formularios a través de la API REST de Gravity Forms?

No, el endpoint /gf/v2/forms/<ID>/submissions no requiere autenticación para el envío. Sin embargo, la API REST de Gravity Forms está desactivada por defecto; necesita habilitarla en los ajustes del plugin (Formularios → Ajustes → API REST → Habilitar acceso a la API).

¿Se puede usar el mismo código JavaScript para Contact Form 7 y Gravity Forms?

Sí, precisamente para eso sirve la normalización de la respuesta. Ambas funciones normalizadoras (para CF7 y GF) devuelven un objeto con la misma estructura: los campos isSuccess, message y validationError. Conecte la función adecuada según el plugin, y todo el código restante (visualización de errores, resaltado de campos, notificación global) funcionará sin cambios.

¿Qué debo hacer si el formulario no se envía y el servidor devuelve un error 404?

Compruebe tres cosas: si la API REST del plugin está habilitada (especialmente relevante para Gravity Forms), si el identificador del formulario en la URL del endpoint es correcto y si la API REST está siendo bloqueada a nivel de servidor o por un plugin de seguridad. Para Contact Form 7, asegúrese también de que la API REST de WordPress esté activa globalmente; sin ella, CF7 no podrá procesar los envíos AJAX.