Como Enviar Dados de Formulário HTML com JavaScript fetch()

HTML form submit fetch javascript tutorial showing code editor with fetch API call and form submission flow

Se você já criou um formulário de contato e viu a página inteira recarregar depois que o usuário clica em "Enviar", você já sabe como essa experiência pode ser frustrante. Usando técnicas de envio de formulário HTML com fetch javascript, você pode enviar dados do formulário para um endpoint backend em segundo plano, manter o usuário na mesma página e exibir uma mensagem limpa de sucesso ou erro sem nenhuma atualização completa da página. Este tutorial te guia por cada etapa, desde escrever o HTML básico até apontar sua chamada fetch() para um endpoint Sendform para que seus envios cheguem diretamente na sua caixa de entrada ou fluxo de trabalho conectado.

Pontos Principais:

  • A API Fetch permite enviar dados de formulário sem recarregar a página, proporcionando uma experiência mais fluida aos usuários.
  • Capturar o evento submit e chamar preventDefault() é a base de qualquer padrão de envio ajax de formulário.
  • O Sendform fornece uma URL de endpoint pronta, então você não precisa de nenhum código backend para receber e armazenar envios.
  • O tratamento adequado de resposta (mostrando feedback de sucesso ou erro) é tão importante quanto enviar os dados corretamente.

Por Que Usar fetch() Ao Invés do Envio Padrão de Formulário

O comportamento padrão de envio de formulário do navegador faz exatamente uma coisa: serializa os campos do formulário, envia uma requisição POST (ou GET) para a URL action, e então carrega qualquer resposta que o servidor retornar. Isso significa que o usuário vê um flash branco, perde sua posição de rolagem e espera uma nova página carregar. Em uma conexão lenta, isso pode parecer quebrado.

A API Fetch resolve isso fazendo requisições HTTP programaticamente, inteiramente em JavaScript, sem navegar para outra página. Isso permite um padrão de envio de formulário sem recarregar página que mantém os usuários engajados e te dá controle total sobre todos os aspectos da experiência do usuário, incluindo estados de carregamento, feedback de validação inline e banners de sucesso animados.

Razões práticas adicionais para preferir fetch():

  • Você pode anexar cabeçalhos personalizados (por exemplo, um token CSRF ou uma chave de autorização) que um formulário HTML simples não consegue enviar.
  • Você pode serializar dados como JSON, FormData, ou strings codificadas em URL dependendo do que o endpoint espera.
  • O tratamento de erro é explícito. Você decide o que "falha" significa e como comunicá-la.
  • Funciona em qualquer site estático, incluindo aqueles hospedados no GitHub Pages, Netlify ou CDN, porque a lógica vive inteiramente no navegador.

Nota: Se você está trabalhando com um construtor de sites como Webflow, WordPress ou Hugo, a mesma abordagem fetch() se aplica. Veja nosso guia sobre como integrar Sendform com seu construtor de sites para dicas específicas da plataforma.

Configuração Básica do Formulário HTML

Antes de escrever uma única linha de JavaScript, você precisa de um formulário HTML bem estruturado. O detalhe chave aqui é que você não precisa de um atributo action apontando para lugar nenhum, porque o JavaScript vai lidar com o envio. Você, porém, quer atributos name significativos em cada input - estes se tornam as chaves dos campos no payload enviado.

<form id="contact-form" novalidate>
  <div>
    <label for="name">Seu Nome</label>
    <input type="text" id="name" name="name" required placeholder="João Silva">
  </div>

  <div>
    <label for="email">Endereço de Email</label>
    <input type="email" id="email" name="email" required placeholder="[email protected]">
  </div>

  <div>
    <label for="message">Mensagem</label>
    <textarea id="message" name="message" rows="5" required></textarea>
  </div>

  <button type="submit">Enviar Mensagem</button>

  <!-- Área de feedback -->
  <div id="form-feedback" aria-live="polite"></div>
</form>

Algumas coisas que vale a pena notar nesta marcação:

  • novalidate no elemento form desabilita as bolhas de validação nativas do navegador, te dando controle total sobre as mensagens de erro em JavaScript.
  • A div id="form-feedback" com aria-live="polite" é onde as mensagens de sucesso e erro vão aparecer. O atributo ARIA garante que leitores de tela anunciem o feedback automaticamente.
  • Cada input tem tanto um id (para a associação com o label) quanto um name (para o payload do formulário).

Capturando o Evento de Envio

O primeiro passo em qualquer envio de formulário javascript é interceptar o comportamento padrão do navegador. Você faz isso escutando o evento submit no elemento do formulário e imediatamente chamando event.preventDefault().

const form = document.getElementById('contact-form');

form.addEventListener('submit', async function (event) {
  event.preventDefault(); // Para a navegação padrão da página

  // Validação básica do lado do cliente
  const name = form.elements['name'].value.trim();
  const email = form.elements['email'].value.trim();
  const message = form.elements['message'].value.trim();

  if (!name || !email || !message) {
    showFeedback('Por favor, preencha todos os campos.', 'error');
    return;
  }

  // Prosseguir para enviar dados (próxima seção)
  await submitForm({ name, email, message });
});

Ao separar a lógica de validação da chamada de rede, você mantém o código legível e fácil de estender. A palavra-chave async no manipulador de evento te permite usar await dentro dele, o que faz a chamada da API Fetch com dados do formulário parecer síncrona e evita cadeias de promessas profundamente aninhadas.

Apontando fetch() Para um Endpoint Sendform

É aqui que o trabalho real acontece. Ao invés de construir seu próprio servidor para receber, armazenar e encaminhar envios de formulário, você pode usar o Sendform como seu backend. Depois de criar um formulário no painel do Sendform, você recebe uma URL de endpoint única. Essa URL é tudo que você precisa.

A função de envio abaixo usa a API FormData para construir o payload, que o Sendform aceita nativamente:

async function submitForm(data) {
  // Substitua esta URL pelo seu endpoint Sendform real
  const SENDFORM_ENDPOINT = 'https://sendform.net/pt/SEU_FORM_ID';

  const formData = new FormData();
  formData.append('name', data.name);
  formData.append('email', data.email);
  formData.append('message', data.message);

  try {
    const response = await fetch(SENDFORM_ENDPOINT, {
      method: 'POST',
      body: formData,
    });

    if (response.ok) {
      showFeedback('Obrigado! Sua mensagem foi enviada.', 'success');
      form.reset();
    } else {
      const errorData = await response.json().catch(() => ({}));
      const errorMsg = errorData.message || 'Algo deu errado. Tente novamente.';
      showFeedback(errorMsg, 'error');
    }
  } catch (networkError) {
    showFeedback('Erro de rede. Verifique sua conexão e tente novamente.', 'error');
  }
}

Decisões chave tomadas neste código:

  • Nenhum cabeçalho Content-Type é definido manualmente. Quando você passa um objeto FormData como o body, o navegador define automaticamente o boundary multipart/form-data correto.
  • A verificação response.ok cobre todos os códigos de status HTTP 2xx, não apenas 200. Isso é mais robusto que comparar response.status === 200.
  • O try/catch externo captura falhas de nível de rede (erros DNS, estado offline) que response.ok nunca veria.
Fluxo de envio de formulário JavaScript fetch apontando para uma URL de endpoint Sendform

Tratando a Resposta - Mensagens de Sucesso e Erro

Enviar os dados é apenas metade do trabalho. Os usuários precisam de feedback imediato e claro. A função auxiliar showFeedback() referenciada acima escreve uma mensagem na div de feedback que você adicionou ao HTML:

function showFeedback(message, type) {
  const feedbackEl = document.getElementById('form-feedback');
  feedbackEl.textContent = message;
  feedbackEl.className = type === 'success' ? 'feedback-success' : 'feedback-error';
}

Isso é intencionalmente mínimo. Em um projeto real você pode trocar textContent por um componente animado ou uma biblioteca de notificação toast, mas o padrão permanece o mesmo: atualizar o DOM baseado no resultado da chamada fetch().

Para cenários mais avançados, como redirecionar para uma página de agradecimento personalizada ou disparar uma automação downstream após o envio, confira nosso artigo sobre como automatizar fluxos de trabalho de formulários com webhooks, Zapier e APIs.

Melhores Práticas e Dicas

Fazer o código funcionar é uma coisa. Colocá-lo em produção de uma forma que se sustente é outra. Aqui estão as dicas mais importantes para manter em mente:

  • Desabilite o botão de envio durante a requisição. Defina button.disabled = true antes de chamar fetch() e reabilite em um bloco finally. Isso previne envios duplicados se o usuário clicar múltiplas vezes.
  • Mostre um estado de carregamento. Mude o texto do botão para "Enviando..." ou adicione uma classe de spinner enquanto a requisição está em andamento. Usuários que não veem feedback frequentemente assumem que nada aconteceu e clicam novamente.
  • Valide no lado do servidor também. Validação do lado do cliente é para experiência do usuário. Sendform e qualquer serviço backend devem tratar todos os dados recebidos como não confiáveis.
  • Use HTTPS em todos os lugares. Enviar dados de formulário por HTTP simples expõe a entrada do usuário em trânsito. Endpoints Sendform são HTTPS por padrão, mas certifique-se de que sua própria página também seja servida por HTTPS.
  • Adicione proteção contra spam. Um campo honeypot ou integração CAPTCHA reduz significativamente envios indesejados. Para um olhar mais profundo neste tópico, veja nosso guia sobre melhores práticas de proteção contra spam para formulários.
  • Teste o caminho de erro deliberadamente. Temporariamente mude a URL do endpoint para algo inválido e confirme que sua mensagem de erro aparece. A maioria dos desenvolvedores só testa o caminho feliz.
  • Mantenha a URL do endpoint fora do controle de versão. Se seu projeto é open source, armazene o endpoint Sendform em uma variável de ambiente ou arquivo de configuração que está listado no .gitignore.

Usuários de site estático: Se seu projeto é um site Hugo, Eleventy ou HTML simples sem servidor, a abordagem fetch() descrita aqui é o método recomendado. Leia mais em nosso guia para tratamento de formulários serverless para sites estáticos.

Conclusão

Substituir um envio de formulário padrão por uma chamada fetch() é uma das melhorias de maior impacto que você pode fazer em qualquer formulário de contato ou captura de leads. O resultado é uma experiência mais rápida e profissional que mantém usuários na sua página e te dá controle total sobre mensagens de feedback. Combine isso com um endpoint Sendform e você elimina a necessidade de qualquer código do lado do servidor completamente. Seu formulário está ativo, seus envios chegam na sua caixa de entrada, e seus usuários nunca veem um recarregamento de página irritante. Crie seu endpoint Sendform gratuito hoje e tenha seu primeiro envio entregue em minutos.

Perguntas Frequentes

Sim. Porque fetch() roda inteiramente no navegador, funciona em sites HTML estáticos, projetos JAMstack e qualquer plataforma que serve HTML. Você não precisa de um servidor próprio. O único requisito é um endpoint (como uma URL Sendform) que possa receber a requisição POST.

fetch() é o substituto moderno para XMLHttpRequest. Usa Promises, suporta async/await, e tem uma API mais limpa. Para novos projetos, fetch() é sempre preferível. Ambos conseguem o mesmo resultado de envio ajax de formulário, mas fetch() requer significativamente menos código boilerplate.

Não. Quando você passa um objeto FormData como o body, o navegador automaticamente define o Content-Type para multipart/form-data e inclui a string boundary correta. Defini-lo manualmente na verdade quebraria a requisição ao omitir esse valor boundary.

Cadastre-se no Sendform, crie um novo formulário no painel e copie a URL de endpoint gerada. Cole essa URL como o destino na sua chamada fetch(). Os envios serão encaminhados para seu endereço de email configurado imediatamente.

Uma falha de rede faz com que a Promise fetch() seja rejeitada, que é capturada pelo bloco try/catch externo no código de exemplo. O usuário vê a mensagem "Erro de rede" que você definiu. O envio não é enfileirado automaticamente; o usuário deve tentar novamente quando a conectividade for restaurada.