Skip to content

Tudo para WordPress, desenvolvimento web — e não só

📤 Envio de formulários no WordPress headless: REST API para Contact Form 7 e Gravity Forms

📤 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:

1https://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):

1https://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.

Exemplo de formulário de contacto com cinco campos

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.

A editar um campo do Gravity Forms com o identificador visível input_3

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:

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);

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:

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 normalização para o Gravity Forms (nota: as chaves de erro recebem o prefixo input_ adicionado para corresponderem às chaves do pedido):

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

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>/submissions nã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, message e validationError. 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.