En pocas palabras: Un plugin de formularios sin frontend registra endpoints REST con register_rest_route() y procesa los envíos server-side, sin imprimir HTML. WordPress trae la REST API activada por defecto desde la 4.7 en /wp-json/: con sanitize_text_field() y wp_mail() armás un endpoint de contacto en menos de 50 líneas de PHP.
Un plugin de formulario API sin interfaz en WordPress registra endpoints REST para recibir y procesar envíos de formularios, sin renderizar ningún HTML en el sitio. El frontend lo armás vos (Next.js, React Native, una PWA) y WordPress queda solo como backend: valida, guarda y dispara notificaciones.
Los formularios backend-only exponen su lógica mediante la API REST de WordPress, que viene activada por defecto desde la versión 4.7 bajo la ruta /wp-json/. En vez de un shortcode que imprime campos en una página, el plugin registra rutas con register_rest_route(), recibe un POST con JSON, sanitiza los datos server-side y devuelve una respuesta. No hay CSS, no hay JavaScript propio, no hay panel visual para el usuario final.
En 30 segundos
- La REST API de WordPress ya trae lo necesario:
register_rest_route()mássanitize_text_field()ywp_mail()te dan un endpoint de contacto funcional en menos de 50 líneas de PHP, sin instalar nada. - Gravity Forms tiene REST API v2 documentada con autenticación por claves y endpoints para entries y forms, según su documentación oficial.
- Contact Form 7 expone su propia ruta REST bajo
/wp-json/contact-form-7/v1/contact-forms/<id>/feedback, así que podés usarlo como backend headless sin tocar su shortcode. - La validación server-side es obligatoria: sin UI no hay validación de cliente que te salve, y el
requiredde HTML5 no existe cuando el que postea es curl. - El error más caro es CORS mal configurado: tu app en otro dominio recibe un 403 opaco y perdés dos horas mirando el Network tab.
¿Cuál es la diferencia entre un formulario con interfaz visual y uno API-only?
Un formulario con interfaz visual (WPForms, CF7 clásico) genera el HTML, el CSS y el JavaScript dentro de WordPress y lo inyecta en una página vía shortcode o bloque. Uno API-only no genera nada visible: expone un endpoint REST y espera que vos armes el frontend donde quieras. La diferencia práctica es quién controla el markup y cuánto peso le sumás a la página.
Ponele que tenés un sitio en Next.js consumiendo WordPress como CMS. Si querés un formulario de contacto, con el enfoque tradicional tendrías que renderizar una página de WordPress adentro de tu app, o replicar el HTML a mano y rezar que el nonce sobreviva. Con el enfoque API-only armás tu componente React, hacés un fetch a /wp-json/tu-plugin/v1/contacto y listo.
| Aspecto | Formulario con UI (WPForms, CF7 shortcode) | Formulario API-only (headless) |
|---|---|---|
| Quién renderiza | WordPress (PHP + shortcode/bloque) | Tu app (React, Vue, Swift, Kotlin) |
| Assets cargados en el front | CSS y JS del plugin en cada página | Cero (WordPress no imprime nada) |
| Validación de cliente | La trae el plugin | La escribís vos en el frontend |
| Validación de servidor | La trae el plugin | Obligatoria, la escribís vos o la hereda el plugin |
| Autenticación típica | Nonce de WordPress | Nonce, Application Password, JWT u OAuth |
| Configuración | Panel visual en wp-admin | Código (PHP) o panel + API híbrido |
| Encaja con apps móviles | No sin webview | Sí, nativo |

Eso sí: la contra del API-only es que perdés el panel donde un cliente no técnico agrega un campo sin llamarte. Si el que mantiene el formulario es alguien de marketing, un plugin sin interfaz es un dolor de cabeza garantizado (vos vas a terminar siendo el «panel de administración»). Tema relacionado: extensiones de la comunidad WordPress.
¿Cuáles son los principales plugins para formularios sin interfaz en WordPress?
Hay cuatro caminos concretos:
- WP Headless Forms (un plugin específicamente pensado para esto)
- Contact Form 7 usando su endpoint REST nativo
- Gravity Forms con su REST API v2 y GFAPI
- Formidable Forms vía webhooks
Los tres últimos son plugins tradicionales que exponen una capa API además de su interfaz, lo cual tiene ventajas y desventajas.
WP Headless Forms: el enfoque minimalista
El proyecto wp-headless-forms en GitHub es de código abierto y se apoya en Advanced Custom Fields para definir los campos. La idea es simple: definís el formulario como estructura de datos, el plugin registra el endpoint REST y guarda los envíos como custom post type. No imprime nada en el frontend porque no tiene frontend.
Es la opción más liviana. También la que más te obliga a escribir código.
Contact Form 7 como backend headless
CF7 registra una ruta REST propia: POST /wp-json/contact-form-7/v1/contact-forms/<form_id>/feedback. Le mandás un FormData con los nombres de campo que definiste en el panel (your-name, your-email, etc.) y devuelve un JSON con status y message. La documentación oficial de CF7 cubre la configuración de los campos y los mail tags.
Es la vía más rápida si ya tenés CF7 instalado y el cliente quiere seguir editando los mensajes de notificación desde wp-admin. Ojo con dos cosas: hay que mandar FormData, no JSON puro, y el campo _wpcf7_unit_tag a veces hace falta para que no se queje. El artículo de Hudson Atwell sobre integrar CF7 y Gravity Forms en una instancia headless documenta esos detalles.
Gravity Forms con REST API v2
Gravity Forms tiene la API más completa del lote. Su REST API v2 vive bajo /wp-json/gf/v2/ y expone endpoints para forms, entries, field filters y submissions. También podés autenticar con claves de API generadas desde el panel (Forms → Settings → REST API), lo cual es cómodo para integraciones server-to-server.
Además de la REST API, tenés GFAPI, la clase PHP para hacer todo desde el servidor: GFAPI::submit_form() corre las validaciones y dispara las notificaciones y los feeds igual que un envío normal. Si querés un endpoint propio pero aprovechando toda la lógica de GF, ese es el combo.
Ninja Forms y Formidable
Ninja Forms también funciona como backend headless. La guía de construcción de un formulario de contacto en WordPress headless con Next.js y Ninja Forms muestra el patrón completo: consultar la estructura del formulario, renderizarlo en React y postear el resultado. Formidable Forms va más por el lado de los webhooks salientes: en vez de que tu app postee a WordPress, WordPress postea a donde vos le digas cuando entra un envío.
¿Cómo implemento un formulario personalizado mediante API REST?
Registrás una ruta con register_rest_route() dentro del hook rest_api_init, definís el método (POST), un permission_callback y un callback que procesa los datos. Adentro del callback sanitizás cada campo, validás, guardás (custom post type o tabla propia) y devolvés un WP_REST_Response. Es todo lo que necesitás para un formulario de contacto.
Un esqueleto mínimo, para poner en un plugin propio o en el functions.php de un child theme: Sobre eso hablamos en crear landing pages con formularios personalizados.
add_action( 'rest_api_init', function () {
register_rest_route( 'mi-plugin/v1', '/contacto', array(
'methods' => 'POST',
'callback' => 'mi_plugin_recibir_contacto',
'permission_callback' => '__return_true', // ver nota abajo
'args' => array(
'nombre' => array(
'required' => true,
'sanitize_callback' => 'sanitize_text_field',
),
'email' => array(
'required' => true,
'sanitize_callback' => 'sanitize_email',
'validate_callback' => function ( $valor ) {
return is_email( $valor ) !== false;
},
),
'mensaje' => array(
'required' => true,
'sanitize_callback' => 'sanitize_textarea_field',
),
),
) );
} );
function mi_plugin_recibir_contacto( WP_REST_Request $request ) {
$post_id = wp_insert_post( array(
'post_type' => 'contacto_entry',
'post_status' => 'private',
'post_title' => $request['nombre'],
'post_content' => $request['mensaje'],
), true );
if ( is_wp_error( $post_id ) ) {
return new WP_Error( 'guardado_fallido', 'No se pudo guardar', array( 'status' => 500 ) );
}
update_post_meta( $post_id, '_email', $request['email'] );
wp_mail(
get_option( 'admin_email' ),
'Nuevo contacto desde la app',
$request['mensaje']
);
return new WP_REST_Response( array( 'ok' => true, 'id' => $post_id ), 201 );
}Fijate el array args: ahí es donde vive la validación de verdad. WordPress corre sanitize_callback y validate_callback antes de que tu función se ejecute, así que cuando llegás al callback los datos ya pasaron por el filtro. Si un campo required falta, la API devuelve un 400 con rest_missing_callback_param sin que vos escribas una línea.
Ese __return_true del permission_callback merece una aclaración: hace que el endpoint sea público, que es lo que querés para un formulario de contacto anónimo. Pero público significa público (cualquiera con curl puede postear mil veces por minuto), así que va acompañado de rate limiting y de algún antispam. Nunca uses __return_true en un endpoint que lea o modifique datos sensibles.
¿Cómo valido y aseguro un formulario sin interfaz?
Toda la validación va del lado del servidor, sin excepción. Sin UI no existe el required de HTML5 ni el pattern del input: lo que llega a tu endpoint es lo que el cliente quiso mandar. Sanitizá con las funciones de WordPress (sanitize_text_field, sanitize_email, sanitize_textarea_field), validá tipos y rangos con validate_callback, y decidí explícitamente quién puede postear con permission_callback.
- Nonce para peticiones desde el mismo dominio. Si tu frontend vive en el mismo WordPress (un tema con JS), pasá
wp_create_nonce('wp_rest')en el headerX-WP-Nonce. La documentación del handbook lo cubre en la sección de autenticación. Si tu app está en otro dominio, el nonce no te sirve: depende de la cookie de sesión. - Application Passwords para server-to-server. Desde WordPress 5.6 vienen en el core y usan HTTP Basic sobre HTTPS. Sirven para integraciones que corren en tu backend, jamás para exponer en un cliente JavaScript (irían en el bundle, o sea, en público).
- JWT u OAuth para apps con usuarios. Si cada envío tiene que estar asociado a un usuario logueado de tu app, necesitás un esquema de tokens. WordPress no lo trae en el core; se resuelve con plugin o con código propio.
- CORS explícito y acotado. Filtrá
rest_pre_serve_requestpara mandarAccess-Control-Allow-Origincon tu dominio exacto, no con*. Un asterisco convierte tu endpoint en un formulario público para cualquier sitio de internet. - Rate limiting. Un endpoint público sin límite de envíos es un imán para bots. Un transient con contador por IP zafa para casos simples; para algo serio, resolvelo a nivel de servidor o WAF.
Si te interesa el hardening completo de WordPress (firewalls, mitigación de fuerza bruta, CVEs de plugins), ese terreno lo cubre nuestro blog hermano seguridadenwordpress.com con mucho más detalle del que entra acá.
¿Cuándo conviene usar un formulario sin interfaz?
Cuando el frontend no lo controla WordPress. Los cuatro escenarios claros: un sitio headless (Next.js, Nuxt, Astro consumiendo la REST API), una app móvil nativa que manda datos a tu backend, una SPA desacoplada donde WordPress es solo la base de datos, y las integraciones donde un servicio externo postea directo a tu endpoint sin pasar por un navegador. En probar el plugin en un ambiente de testing profundizamos sobre esto.
Un caso real: una agencia con un catálogo en Next.js y el contenido en WordPress. El formulario de presupuesto tiene que aparecer en un modal de React con validación en vivo, autocompletado de dirección y un paso de carga de archivos. Meterle CF7 con shortcode ahí significaría un iframe o un scraping del HTML generado, dos cosas horribles. Con un endpoint REST propio, el modal es un componente más y WordPress solo recibe el JSON.
¿Y cuándo NO conviene? Cuando tenés un sitio WordPress clásico y el cliente quiere cambiar un campo del formulario solo. Ahí el plugin con panel visual gana por goleada. No te hagas el héroe escribiendo un endpoint custom para un formulario de contacto de tres campos en un sitio que ya corre WordPress de punta a punta.
Una nota de infraestructura: los endpoints REST son PHP ejecutándose, así que no los cachea nadie (ni deberían). Si vas a recibir volumen, asegurate de que tu hosting aguante peticiones dinámicas sostenidas y no solo HTML cacheado. En un hosting WordPress de Donweb podés dejar el backend REST andando mientras el frontend headless vive en otro lado, que es el esquema que más se usa hoy.
¿Cómo integro los envíos con servicios externos y webhooks?
Con wp_remote_post() dentro del callback del endpoint, o enganchado a un hook posterior para no bloquear la respuesta. Cuando entra un envío, armás el payload JSON y lo mandás al CRM (HubSpot, Zoho), a Slack, o a un webhook de Zapier. Gravity Forms y Formidable traen webhooks configurables desde el panel; con un endpoint propio, lo escribís vos.
El patrón que recomiendo: no hagas la llamada HTTP externa dentro del request del formulario. Si el CRM tarda tres segundos, tu usuario espera tres segundos y si el CRM está caído, tu formulario «falla» aunque el dato ya esté guardado. Guardá primero, respondé 201, y dispará el envío externo con un do_action() propio enganchado a wp_schedule_single_event() o a Action Scheduler. Te puede servir nuestra cobertura de validar que los formularios no generen errores.
// Dentro del callback, después de guardar:
do_action( 'mi_plugin_contacto_guardado', $post_id );
// En otro lado, el envío al CRM:
add_action( 'mi_plugin_contacto_guardado', function ( $post_id ) {
wp_schedule_single_event( time() + 5, 'mi_plugin_push_crm', array( $post_id ) );
} );
add_action( 'mi_plugin_push_crm', function ( $post_id ) {
$respuesta = wp_remote_post( 'https://api.ejemplo.com/leads', array(
'headers' => array(
'Content-Type' => 'application/json',
'Authorization' => 'Bearer ' . MI_PLUGIN_CRM_TOKEN,
),
'body' => wp_json_encode( array(
'email' => get_post_meta( $post_id, '_email', true ),
'mensaje' => get_post_field( 'post_content', $post_id ),
) ),
'timeout' => 15,
) );
if ( is_wp_error( $respuesta ) ) {
error_log( 'Push al CRM falló: ' . $respuesta->get_error_message() );
}
} );Para CF7 existe el plugin «Contact Form 7 to Any API», que hace este mapeo desde un panel sin escribir PHP. Si el destino es uno solo y el payload es simple, zafa perfecto.
Errores comunes al implementar formularios headless
- Confiar en la validación del frontend. Tu componente React valida el email con una regex hermosa y el bot postea con curl directo al endpoint ignorando todo. Si el
validate_callbackno está en PHP, la validación no existe. - Mandar
Access-Control-Allow-Origin: *para «arreglar» CORS. Funciona, sí. También le regala tu endpoint a cualquier sitio que quiera usarlo. Poné el dominio exacto y agregá los métodos y headers que tu app usa de verdad. - Poner
__return_trueen todos lospermission_callback. Para un contacto anónimo está bien; para un endpoint que lee entries, es un agujero. Si copiás el snippet de un tutorial sin mirar esa línea, vas a exponer datos. - Asumir que la REST API está habilitada. Muchos plugins de seguridad y varias configuraciones de hardening la bloquean para usuarios no autenticados. Antes de debuggear tu código tres horas, pegale un
curla/wp-json/y confirmá que responde. - No manejar el error en el frontend. WordPress devuelve un JSON con
codeymessagecuando algo falla. Si tufetchsolo mira el.oky muestra «Error», el usuario nunca sabe que puso mal el email. - Mezclar endpoints públicos y privados en el mismo namespace sin criterio. Registrar
/contacto(público) y/entries(privado) bajo el mismo prefijo invita a que alguien copie elpermission_callbackequivocado en el próximo refactor.
Preguntas Frecuentes
¿Qué es un plugin de formulario sin interfaz en WordPress?
Es un plugin que expone formularios solo mediante endpoints de la API REST, sin renderizar HTML en el sitio. Recibe los datos por POST, los valida y sanitiza del lado del servidor, y los guarda o los reenvía. El frontend lo construye el desarrollador por fuera de WordPress.
¿Puedo usar Contact Form 7 como backend para aplicaciones headless?
Sí. CF7 registra la ruta POST /wp-json/contact-form-7/v1/contact-forms/<id>/feedback, que acepta un FormData con los nombres de campo configurados en el panel y devuelve JSON con el estado del envío. Configurás el formulario y las notificaciones desde wp-admin como siempre, y el frontend lo armás donde quieras.
¿Cuánto cuesta un plugin de formularios con API?
Contact Form 7 y WP Headless Forms son gratuitos y de código abierto. Gravity Forms, Formidable Forms y Ninja Forms son comerciales con licencia anual, y el acceso a su API completa suele estar en los planes superiores. Escribir tu propio endpoint con register_rest_route() no cuesta nada más que tu tiempo.
¿Necesito un plugin o puedo hacerlo con la REST API sola?
Podés hacerlo con la REST API sola. Un endpoint de contacto funcional necesita register_rest_route(), un array de args con sanitización, wp_insert_post() para guardar y wp_mail() para notificar: menos de 50 líneas de PHP. El plugin te ahorra tiempo cuando querés lógica condicional, pagos o feeds a CRMs.
¿Por qué mi endpoint REST devuelve 403 desde mi app?
Casi siempre es CORS o el nonce. Si tu app corre en otro dominio, el nonce de WordPress no funciona porque depende de la cookie de sesión, y el navegador bloquea el preflight OPTIONS si no mandás los headers Access-Control-Allow-* correctos. Probá el mismo POST con curl: si funciona, el problema es del navegador, no del endpoint.
Conclusión
Si tu frontend no es WordPress, tu formulario tampoco tiene por qué serlo. La REST API del core te da todo lo necesario para armar un endpoint de contacto en una tarde, y los plugins grandes (Gravity Forms con su v2, CF7 con su ruta feedback) ya exponen la capa API si preferís no reinventar la validación y las notificaciones.
Lo que hay que tener claro es que la comodidad del headless viene con una factura: toda la validación, la autenticación y el CORS pasan a ser tu responsabilidad. Los tres errores que más veo (validar solo en el cliente, poner el asterisco en Allow-Origin, y copiar un permission_callback sin leerlo) se evitan revisando esas tres líneas antes de deployar. Arrancá con el snippet de arriba, pegale un curl al endpoint sin token para ver qué te devuelve, y recién ahí conectá tu app.
Fuentes
- WordPress REST API Handbook – documentación oficial de rutas, autenticación y endpoints personalizados
- Gravity Forms REST API v2 – documentación oficial de endpoints, autenticación y GFAPI
- Contact Form 7 Docs – configuración de formularios, campos y mail tags
- WP Headless Forms – plugin open source de formularios backend-only basado en ACF
- Cómo integrar Contact Form 7 y Gravity Forms en una instancia headless de WordPress




