
📤 Envio de formulários no WordPress headless: REST API para Contact Form 7 e Gravity Forms
Está a construir um site em WordPress e um formulário de contacto já está resolvido. Plugins como o Contact Form 7 oferecem HTML pronto, validação, armazenamento de submissões e dezenas de integrações. Clique em «Instalar», cole o shortcode e está pronto em dois minutos.
Mas tudo muda quando o WordPress se torna um CMS headless. Passa a ser responsável por todo o frontend: React, Vue, HTML/JS simples. E o plugin de formulários que antes gerava o markup por si já não controla o lado do cliente. No entanto, a sua API REST continua lá. Basta enviar um POST para o endpoint certo e todo o poder do plugin (validação de campos, armazenamento, integrações) permanece à sua disposição.
Na prática, também pode cobrir casos puramente «tradicionais» através da API REST dos plugins de formulários. Imagine que está a construir um tema personalizado com Tailwind e o markup fixo do CF7, com a sua estrutura rígida de classes, parece deslocado. Submeter através da API permite-lhe controlar cada píxel do formulário sem abandonar o ecossistema estabelecido do plugin.
💡 Visão geral rápida:
- Que endpoints o Contact Form 7 e o Gravity Forms disponibilizam e como ativá-los num ambiente headless.
- Que formato usar ao enviar os campos para que o plugin aceite corretamente os dados e devolva um resultado.
- Como construir um formulário HTML, anexar um pedido fetch e mostrar ao utilizador uma mensagem de sucesso ou erros de validação.
- Como unificar os diferentes formatos de resposta do CF7 e do Gravity Forms numa única estrutura conveniente.
O que precisa de saber sobre os endpoints
Enviar dados através da API REST é a parte tecnicamente simples. Ambos os plugins esperam um POST para um endpoint onde o segmento dinâmico do URL é o identificador do formulário específico.
O Contact Form 7 disponibiliza uma API REST imediatamente após a ativação. O endpoint tem este aspeto:
1 https://your-site.tld/wp-json/contact-form-7/v1/contact-forms/<FORM_ID>/feedback
A partir da versão 5.8 (agosto de 2023), o Contact Form 7 passou a usar identificadores de formulário com hash SHA-1. Os antigos IDs numéricos ainda funcionam, mas para novos formulários precisa de obter o identificador a partir do URL da página de edição do formulário no painel de administração (o último segmento após post=). Em abril de 2026, o plugin tem mais de 10 milhões de instalações ativas e está testado até ao WordPress 7.0.
O Gravity Forms usa a API REST v2 (disponível desde a versão 2.4):
1 https://your-site.tld/wp-json/gf/v2/forms/<FORM_ID>/submissions
Nota importante: a API REST do Gravity Forms está desativada por predefinição. Para a ativar, vá às definições do plugin → separador REST API → marque «Enable access to the API». Não é necessária uma chave de API para o endpoint de submissão de formulários; é público por definição. O identificador do formulário no Gravity Forms é numérico e visível no painel de administração durante a edição.
Estrutura do corpo do pedido
Vamos usar um formulário de exemplo com cinco campos: texto obrigatório, email e data (anterior a 4 de outubro de 1957), uma área de texto opcional e uma caixa de verificação obrigatória.

O Contact Form 7 espera chaves no formato definido através da sintaxe de tags de formulário. A chave corresponde ao name do campo respetivo em 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 }
O Gravity Forms utiliza uma abordagem diferente: identificadores incrementais gerados automaticamente com o prefixo input_. O ID do campo é visível no próprio painel de administração ao editar um campo específico.

Para o mesmo formulário, o corpo do pedido no Gravity Forms é 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 }
Ponto essencial: se atribuir aos seus inputs HTML atributos name que correspondam às chaves esperadas pelo plugin, o mapeamento acontece automaticamente e o FormData recolherá os dados no formato correto, sem mapeamento manual.
Construir o HTML e enviar o pedido
Para o Contact Form 7, a marcação HTML tem este aspeto (note que action é o endpoint e os atributos name dos campos correspondem às chaves acima):
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 o Gravity Forms, apenas mudam o action e os 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>
Agora, quanto ao envio via JavaScript: o FormData recolhe os valores por name automaticamente, pelo que não é necessário qualquer mapeamento:
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);
Os dados são enviados. Mas isso não chega para o utilizador; ele precisa de feedback: uma mensagem de sucesso, campos com erros destacados, uma notificação global. Felizmente, ambos os plugins devolvem esta informação na resposta.
Validação: o servidor decide, o cliente apresenta
Para além da validação HTML5 integrada (atributos como required, type="email" e max), faz sentido confiar na verificação de regras do lado do servidor que os plugins fornecem. Porquê: as regras são configuradas centralmente na administração do WordPress e duplicá-las no cliente significa trabalho a dobrar e uma fonte de inconsistências.
Tanto o Contact Form 7 como o Gravity Forms devolvem os erros de validação diretamente no corpo da resposta. Para cenários complexos (campos condicionais, validação dependente), confiar na validação do servidor é especialmente vantajoso: não precisa de sincronizar a lógica entre o frontend e as configurações do plugin.
A tarefa resume-se a três passos: analisar a resposta JSON, extrair as mensagens de erro e inseri-las no DOM junto aos campos correspondentes.
Formatos de resposta e normalização
Resposta do Contact Form 7 em caso de erro de validação:
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 }
Em caso de sucesso, a resposta é mais 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 }
A resposta do Gravity Forms em caso de erro de validação tem uma estrutura 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 }
E uma resposta bem-sucedida contém a confirmação 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 }
A diferença de abordagens é óbvia: o CF7 incorpora os erros num array de objetos com seletores CSS, enquanto o Gravity Forms utiliza um objeto simples com chaves numéricas sem o prefixo input_. A mensagem de sucesso do Gravity Forms vem encapsulada em HTML. As chaves dos campos nas respostas do CF7 estão embutidas nos seletores (por exemplo, span.wpcf7-form-control-wrap.somebodys-name) e exigem extração via regex.
Em vez de criar lógica condicional para cada plugin, é mais conveniente normalizar ambas as respostas para um 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 }
Em caso de sucesso, isSuccess é definido como true e validationError é um objeto vazio.
Código de normalização para o 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 };
Código de normalização para o Gravity Forms (nota: as chaves de erro recebem o prefixo input_ adicionado para corresponderem às chaves do pedido):
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 };
Agora tem um objeto de resposta unificado, independentemente do plugin. Resta apenas escrever a exibição de erros e a alternância de classes nos elementos do DOM, e o formulário está pronto a usar.
Da normalização para uma interface reativa
Depois de a resposta ser convertida para uma estrutura unificada, a apresentação do feedback resume-se a manipulação do DOM. Adicionar uma mensagem de erro junto a um campo, alternar uma classe num wrapper e mostrar uma notificação global: estas três ações são suficientes para a grande maioria dos cenários.
Para atualizações reativas da interface, bibliotecas declarativas leves como o Alpine.js são práticas. Sintaxe mínima, sem etapa de compilação e integração natural com respostas do servidor tornam-na uma escolha prática para formulários num ambiente headless. A abordagem com Alpine.js foi abordada em detalhe no CSS-Tricks; o código desse artigo funciona quase sem alterações com a resposta normalizada que obtivemos acima.
O resultado final
Replicar a funcionalidade do lado do cliente que os plugins de formulários oferecem «pronta a usar» representa algumas horas de trabalho para formulários simples. Uma vantagem adicional: ao abstrair a resposta através de uma função normalizadora, obtém um backend intercambiável. Mudar do Contact Form 7 para o Gravity Forms (ou vice-versa) pode ser feito sem alterações no frontend; basta substituir o endpoint e a função normalizadora.
Formulários com vários passos, pré-visualização de imagens carregadas, calculadoras de preços: sim, isso já é desenvolvimento a sério. Mas quanto mais específicos forem os requisitos de um projeto, mais forte é o argumento a favor de um frontend personalizado sobre a REST API: não está a lutar contra o markup de terceiros nem a contornar as limitações da renderização pré-construída.
A abordagem headless para formulários não é um futuro hipotético. Atualmente, plugins como o Contact Form 7 e o Gravity Forms disponibilizam REST APIs completas, e as frameworks de frontend permitem construir um formulário em horas, em vez de dias. Experimente no seu próximo projeto onde a aparência do formulário seja crítica: utilize o CF7 ou o GF como backend e construa a interface de raiz. É provável que fique surpreendido com a simplicidade do processo.
⁉️🤔 Perguntas frequentes
A REST API do Contact Form 7 funciona com a versão gratuita?
Sim, a REST API fica disponível imediatamente após ativar o plugin gratuito; não é necessária qualquer configuração adicional. Em abril de 2026, o Contact Form 7 conta com mais de 10 milhões de instalações ativas, e a REST API tem sido uma parte estável do núcleo do plugin desde a versão 4.8.
O que muda nos IDs de formulário com hash nas versões mais recentes do Contact Form 7?
A partir da versão 5.8 (agosto de 2023), o CF7 gera um hash SHA-1 como identificador do formulário, em vez de um ID numérico. Os IDs numéricos antigos continuam a funcionar. Pode encontrar o hash no URL da página de edição do formulário no painel de administração:
/wp-admin/admin.php?page=wpcf7&post=<HASH>&action=edit. É substituído no endpoint da mesma forma que um ID numérico.
É necessária uma chave de API para enviar formulários através da REST API do Gravity Forms?
Não, o endpoint
/gf/v2/forms/<ID>/submissionsnão requer autenticação para submissão. No entanto, a própria REST API do Gravity Forms está desativada por predefinição; precisa de a ativar nas definições do plugin (Formulários → Definições → REST API → Ativar o acesso à API).
O mesmo código JavaScript pode ser usado tanto para o Contact Form 7 como para o Gravity Forms?
Sim, é exatamente para isso que serve a normalização da resposta. Ambas as funções normalizadoras (para CF7 e GF) devolvem um objeto com a mesma estrutura: campos
isSuccess,messageevalidationError. Utilize a função apropriada consoante o plugin, e todo o restante código (exibição de erros, destaque de campos, notificação global) funcionará sem alterações.
O que devo fazer se o formulário não for submetido e o servidor devolver um erro 404?
Verifique três pontos: se a REST API do plugin está ativada (especialmente relevante para o Gravity Forms), se o identificador do formulário no URL do endpoint está correto e se a REST API está a ser bloqueada ao nível do servidor ou por um plugin de segurança. No caso do Contact Form 7, certifique-se também de que a REST API do WordPress está globalmente ativa; sem ela, o CF7 não conseguirá processar submissões AJAX.



