Envio de formulários HTML

Envio de formulários HTML7 min de leitura

Validação de formulários HTML no navegador e no servidor

Valide um formulário com required, type, pattern e min, estilize erros com :user-invalid e aplique as mesmas regras no servidor com definições de campos.

Preet SutharFundador, Ikiform

Use as restrições nativas do HTML, como required, type="email", min, max e pattern, para que o navegador barre dados inválidos antes de qualquer envio. Depois aplique as mesmas regras no servidor, porque bots e scripts pulam o navegador por completo. Com o Ikiform, defina os campos do formulário e todo envio é conferido: um inválido recebe 422 validation_failed com uma mensagem por campo, que você pode mostrar com setCustomValidity.

O exemplo é um formulário de reserva de workshop. Ele tem o tipo de campo em que a validação faz diferença: um tamanho de equipe com limites, uma data que não pode estar no passado, um telefone num único formato e um código de reserva com formato fixo.

O que muda aqui

Existem dois validadores, e eles não veem as mesmas requisições. O navegador confere só as pessoas que usam a sua página, com ou sem scripts. O servidor confere tudo, inclusive bots enviando direto ao endpoint e o seu próprio código chamando form.submit(), que pula as checagens do navegador. O trabalho é fazer os dois dizerem a mesma coisa.

Um envio passando pela validação do navegador e depois pela validação de campos do Ikiformuma pessoa na sua páginaum bot ou um scriptenviarrestriçõesdonavegadorrequired, type,min e patterncampos doIkiformas mesmasregras,aplicadasarmazenadovalorestipadosbalão nocamponada enviadoPOSTdiretosem página, semchecagenscampos doIkiform422validation_failednadaarmazenadoinválidoUm envio passando pela validação do navegador e depois pela validação de campos do Ikiformuma pessoa na sua páginaum bot ou um scriptenviarrestrições donavegadorrequired, type, min epatterncampos doIkiformas mesmas regras,aplicadasarmazenadovalores tipadosbalão nocamponadaenviadoPOST diretosem página, semchecagenscampos doIkiform422validation_failednada armazenadoinválido
  1. Uma pessoa envia o formulário, e o navegador confere as restrições do HTML. Um campo inválido recebe uma mensagem e nada é enviado.
  2. Um envio válido chega ao Ikiform, que o confere com a definição de campos do formulário e armazena valores tipados.
  3. Um bot ou script envia direto ao endpoint, então nenhuma checagem do navegador roda.
  4. O Ikiform o confere com a mesma definição e o recusa com 422 antes de armazenar qualquer coisa.
A camada do navegador é para as pessoas. A camada do servidor é a que realmente vale.

A camada do navegador

Prévia

Roda o validador do Ikiform no seu navegador. Nada é enviado.

A definição que o Ikiform aplica. Teste um tamanho de equipe de 25, uma data antes de 2 de novembro ou um código como ws-12.
workshop.html
<form id="workshop" action="https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx" method="post">
  <label for="name">Your name</label>
  <input id="name" name="name" autocomplete="name" maxlength="120" required />

  <label for="email">Work email</label>
  <input id="email" name="email" type="email" autocomplete="email" required />

  <label for="team_size">People attending</label>
  <input id="team_size" name="team_size" type="number" min="2" max="20" step="1" required />

  <label for="date">Preferred date</label>
  <input id="date" name="date" type="date" min="2026-11-02" required />

  <label for="phone">Phone</label>
  <input id="phone" name="phone" type="tel" autocomplete="tel" placeholder="+14155550123"
    pattern="\+[1-9][0-9]{6,14}" title="International format, like +14155550123" />

  <label for="code">Booking code</label>
  <input id="code" name="code" pattern="WS-[0-9]{4}" title="Codes look like WS-1234" />

  <input name="_gotcha" type="text" tabindex="-1" autocomplete="off" hidden />
  <button>Request the workshop</button>
</form>

Cada atributo corresponde a um estado de validade, que você pode ler em input.validity no console:

AtributoEstado de validadeObservações
requiredvalueMissingNum grupo de radio, um radio com required cobre o grupo
type="email", type="url"typeMismatchPermissivo: a@b passa como e-mail nos navegadores
patternpatternMismatchPrecisa casar com o valor inteiro. Ignorado num campo opcional vazio
minlength, maxlengthtooShort, tooLongSó conferido depois que a pessoa edita o valor
min, maxrangeUnderflow, rangeOverflowNúmeros, datas e horários
stepstepMismatchstep="1" recusa decimais
Entrada que não pode ser interpretadabadInputLetras digitadas num campo numérico em alguns navegadores
setCustomValidity()customErrorQualquer mensagem que você definir, até voltar a defini-la como vazia

Patterns em dois motores

Os navegadores compilam pattern com a flag v, que é mais rígida que as expressões regulares que a maioria das pessoas escreve: um - literal dentro de uma classe de caracteres precisa ser escapado, como em [a-z\-]. O Ikiform compila o pattern de um campo com a flag u e também casa com o valor inteiro. Escreva patterns válidos nos dois, como o código de reserva acima, e teste cada um na prévia do formulário antes de publicar.

Estilizando erros

:invalid casa com um campo obrigatório vazio no momento em que a página carrega, então todo campo aparece em vermelho antes de alguém digitar. :user-invalid só casa depois que a pessoa interagiu com o campo ou tentou enviar, que é quase sempre o que você quer:

workshop.html
<style>
  #workshop :user-invalid {
    border-color: #b42318;
  }
</style>

A camada do servidor

Cole a definição acima na aba Fields (campos) do formulário. A partir daí, o Ikiform valida todo envio com base nela, venha de onde vier, e armazena valores convertidos: team_size como número, phone sem separadores, date como YYYY-MM-DD. Um envio que quebra uma regra é recusado, nada é armazenado, e a resposta diz qual campo falhou:

CampoEnviadoMensagem
team_size25Must be at most 20
team_size2.5Enter a whole number
date2026-10-30Must be on or after 2026-11-02
phone415 555 0123Use international format, e.g. +14155550123
codews-12Codes look like WS-1234
websiteQualquer valorUnknown field "website"

A última linha importa. Com campos definidos, o Ikiform recusa qualquer campo que não está na definição, então um input renomeado falha de forma visível em vez de ser armazenado com o nome errado. Campos que começam com _ e o honeypot não contam. Todos os tipos e regras estão em Campos e validação.

Com um envio simples

Não precisa de código. Um envio simples recusado mostra a página de erro hospedada do Ikiform com o status 422, cada problema sob o rótulo do seu campo e um link para a página de onde o envio veio. O botão Back (voltar) do navegador geralmente mantém o que a pessoa digitou; o link recarrega o formulário vazio.

Mostrando erros do servidor nos campos

Com um script, passe cada mensagem do servidor para a interface de erro do próprio navegador. setCustomValidity marca o campo como inválido com esse texto, e reportValidity o mostra no balão de sempre e foca o primeiro:

workshop.js
const form = document.querySelector("#workshop");

for (const control of form.elements) {
  control.addEventListener("input", () => control.setCustomValidity?.(""));
}

form.addEventListener("submit", async (event) => {
  event.preventDefault();
  const response = await fetch(form.action, {
    method: "POST",
    headers: { Accept: "application/json" },
    body: new FormData(form),
  });
  const result = await response.json();
  if (result.ok) {
    form.replaceWith("Thanks. We'll confirm the date by email.");
    return;
  }
  const fields = result.error.fields ?? {};
  for (const [name, messages] of Object.entries(fields)) {
    form.elements.namedItem(name)?.setCustomValidity?.(messages[0]);
  }
  if (!form.reportValidity()) return;
  alert(result.error.message);
});
  • Limpe a mensagem personalizada no input. Um campo com erro personalizado continua inválido até você voltá-lo para uma string vazia, e enquanto isso a pessoa não consegue enviar.
  • As checagens do navegador ainda rodam antes do evento submit, então o servidor só vê envios que passaram por elas.
  • Se o erro não tem fields, como 429 rate_limited, reportValidity() devolve true e o script recorre à mensagem geral.

Conferindo regras do servidor antes de enviar

Para pegar regras que só existem no servidor, como uma lista de domínios de e-mail, antes da requisição, carregue a definição pública do formulário e rode o validate do SDK, que aplica as mesmas regras e mensagens de campo no navegador:

workshop.js
import { getSchema, validate } from "@ikiform/sdk";

const form = document.querySelector("#workshop");
const result = await getSchema("frm_xxxxxxxxxxxxxxxx");
const fields = result.ok ? result.schema.fields : [];

form.addEventListener("submit", (event) => {
  const errors = validate(fields, new FormData(form));
  for (const control of form.elements) control.setCustomValidity?.(errors[control.name] ?? "");
  if (!form.reportValidity()) event.preventDefault();
});
Trate isso como uma conveniência. O servidor continua sendo a fonte da verdade, e getSchema só expõe definições de campos, que são públicas de qualquer forma: qualquer um pode lê-las em /f/<formId>/schema.

Confira se funciona

  1. Em Allowed domains do formulário, adicione o domínio do seu site, mais localhost enquanto desenvolve. Deixe vazio para aceitar envios de qualquer lugar.
  2. Envie o formulário vazio. O navegador o barra em Seu nome e nada aparece no painel Network.
  3. Remova o max do tamanho da equipe nas ferramentas de desenvolvedor, digite 25 e envie. O envio simples mostra a página de erro do Ikiform; o envio por script mostra Must be at most 20 no campo.
  4. Envie com curl, pulando o navegador: curl -H "Accept: application/json" -H "Origin: https://example.com" -d team_size=abc https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx, com o seu próprio domínio permitido como origem. A resposta é 422 com uma mensagem para team_size e para cada campo obrigatório que faltou.
  5. Abra Submissions no painel. O novo envio está lá com todos os campos que você enviou.

Pegadinhas

ProblemaCausa e solução
O formulário nunca envia depois de um erro do servidorUma mensagem de validade personalizada nunca foi limpa. Zere-a com setCustomValidity("") no input
minlength não dispara em valores pré-preenchidostooShort só vale depois que a pessoa edita o campo. A regra do servidor pega o caso
Um pattern é ignorado por completoEle é inválido sob a flag v, e os navegadores ignoram um pattern inválido por completo. Escape - e outros caracteres especiais dentro de classes
Os erros aparecem antes de alguém digitarO CSS usa :invalid. Troque por :user-invalid
422 validation_failedO envio não corresponde à definição de campos do formulário. error.fields aponta cada campo e o motivo. Faça o name do input bater com o id do campo.

O fluxo completo de envio está na visão geral, e Erros lista todos os códigos que um envio pode receber.

Perguntas

Travou em algum passo ou quer um guia para o seu caso? Envie um e-mail para hello@ikiform.com.