Envío de formularios HTML

Envío de formularios HTML7 min de lectura

Validación de formularios HTML en el navegador y en el servidor

Valida un formulario con required, type, pattern y min, da estilo a los errores con :user-invalid y aplica las mismas reglas en el servidor con definiciones de campos.

Preet SutharFundador, Ikiform

Usa las restricciones integradas de HTML, como required, type="email", min, max y pattern, para que el navegador bloquee la entrada incorrecta antes de enviar nada. Después aplica las mismas reglas en el servidor, porque los bots y los scripts se saltan el navegador por completo. Con Ikiform, define los campos del formulario y se comprueba cada envío: uno incorrecto recibe 422 validation_failed con un mensaje por campo, que puedes mostrar con setCustomValidity.

El ejemplo es un formulario de reserva de un taller. Tiene el tipo de campos donde la validación se gana el sueldo: un tamaño de equipo con límites, una fecha que no puede estar en el pasado, un teléfono en un formato y un código de reserva con una forma fija.

Qué cambia aquí

Hay dos validadores, y no ven las mismas peticiones. El navegador solo comprueba a las personas que usan tu página, con scripts o sin ellos. El servidor lo comprueba todo, incluidos los bots que envían directamente al endpoint y tu propio código llamando a form.submit(), que se salta las comprobaciones del navegador. El trabajo consiste en que los dos digan lo mismo.

Un envío que pasa por la validación del navegador y luego por la validación de campos de Ikiformuna persona en tu páginaun bot o un scriptenvíorestriccionesdelnavegadorrequired, type,min y patterncampos deIkiformlas mismasreglas,aplicadasguardadovalorestipadosmensaje enel campono se envíanadaPOSTdirectosin página, sincomprobacionescampos deIkiform422validation_failedno se guardanadano válidoUn envío que pasa por la validación del navegador y luego por la validación de campos de Ikiformuna persona en tu páginaun bot o un scriptenvíorestriccionesdel navegadorrequired, type, min ypatterncampos deIkiformlas mismas reglas,aplicadasguardadovalores tipadosmensajeen elcampono se envíanadaPOST directosin página, sincomprobacionescampos deIkiform422validation_failedno se guarda nadano válido
  1. Una persona envía el formulario y el navegador comprueba las restricciones del HTML. Un campo no válido recibe un mensaje y no se envía nada.
  2. Un envío válido llega a Ikiform, que lo comprueba con la definición de campos del formulario y guarda valores tipados.
  3. Un bot o un script envía directamente al endpoint, así que no se ejecuta ninguna comprobación del navegador.
  4. Ikiform lo comprueba con la misma definición y lo rechaza con 422 antes de guardar nada.
La capa del navegador es para las personas. La capa del servidor es la que se cumple.

La capa del navegador

Vista previa

Ejecuta el validador de Ikiform en tu navegador. No se envía nada.

La definición que aplica Ikiform. Prueba un tamaño de equipo de 25, una fecha anterior al 2 de noviembre o un 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 un estado de validez, que puedes leer desde input.validity en la consola:

AtributoEstado de validezNotas
requiredvalueMissingEn un grupo de radios, un radio con required cubre todo el grupo
type="email", type="url"typeMismatchPermisivo: a@b pasa como email en los navegadores
patternpatternMismatchDebe coincidir con el valor completo. Se ignora en un campo opcional vacío
minlength, maxlengthtooShort, tooLongSolo se comprueba después de que la persona edite el valor
min, maxrangeUnderflow, rangeOverflowNúmeros, fechas y horas
stepstepMismatchstep="1" rechaza decimales
Entrada que no se puede interpretarbadInputLetras escritas en un campo numérico en algunos navegadores
setCustomValidity()customErrorCualquier mensaje que pongas, hasta que lo vuelvas a dejar vacío

Patrones en dos motores

Los navegadores compilan pattern con el flag v, que es más estricto que las expresiones regulares que escribe la mayoría: un - literal dentro de una clase de caracteres debe escaparse, como en [a-z\-]. Ikiform compila el pattern de un campo con el flag u y también lo compara con el valor completo. Escribe patrones válidos en ambos, como el del código de reserva de arriba, y prueba cada uno en la vista previa del formulario antes de publicarlo.

Dar estilo a los errores

:invalid coincide con un campo obligatorio vacío en cuanto carga la página, así que todos los campos se ven en rojo antes de que nadie escriba. :user-invalid solo coincide después de que la persona haya interactuado con el campo o intentado enviar, que es casi siempre lo que quieres:

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

La capa del servidor

Pega la definición de arriba en la pestaña Fields del formulario. A partir de entonces, Ikiform valida cada envío con ella, venga de donde venga, y guarda valores convertidos: team_size como número, phone sin separadores, date como YYYY-MM-DD. Un envío que incumple una regla se rechaza, no se guarda nada y la respuesta dice qué campo falló:

CampoEnviadoMensaje
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
websiteCualquier valorUnknown field "website"

La última fila importa. Con campos definidos, Ikiform rechaza cualquier campo que no esté en la definición, así que un input renombrado falla de forma visible en lugar de guardarse con el nombre equivocado. Los campos que empiezan por _ y el honeypot no cuentan. Todos los tipos y reglas están en Campos y validación.

Con un envío normal

No hace falta código. Un envío normal rechazado muestra la página de error alojada de Ikiform con el estado 422, cada problema bajo la etiqueta de tu campo y un enlace a la página desde la que se hizo el envío. El botón Atrás del navegador suele conservar lo que escribió la persona; el enlace vuelve a cargar el formulario vacío.

Mostrar los errores del servidor en los campos

Con un script, pasa cada mensaje del servidor a la propia interfaz de errores del navegador. setCustomValidity marca el campo como no válido con ese texto, y reportValidity lo muestra como el mensaje habitual y da el foco al primero:

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);
});
  • Borra el mensaje personalizado en input. Un campo con un error personalizado sigue siendo no válido hasta que lo vuelvas a dejar como cadena vacía, y mientras tanto la persona no puede enviar en absoluto.
  • Las comprobaciones del navegador siguen ejecutándose antes del evento submit, así que el servidor solo ve envíos que las pasaron.
  • Si el error no tiene fields, como 429 rate_limited, reportValidity() devuelve true y el script recurre al mensaje general.

Comprobar las reglas del servidor antes de enviar

Para detectar reglas que solo existen en el servidor, como una lista de dominios de email, antes de la petición, carga la definición pública del formulario y ejecuta validate del SDK, que aplica las mismas reglas y mensajes de campo en el 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();
});
Tómalo como una comodidad. El servidor sigue siendo la fuente de verdad, y getSchema solo expone definiciones de campos, que de todos modos son públicas: cualquiera puede leerlas en /f/<formId>/schema.

Comprueba que funciona

  1. En Allowed domains del formulario, añade el dominio de tu sitio, y localhost mientras desarrollas. Déjalo vacío para aceptar envíos desde cualquier lugar.
  2. Envía el formulario vacío. El navegador lo detiene en Tu nombre y no aparece nada en el panel Network.
  3. Quita max del tamaño de equipo en las herramientas de desarrollo, escribe 25 y envía. El envío normal muestra la página de error de Ikiform; el envío con script muestra Must be at most 20 en el campo.
  4. Envía con curl, saltándote el navegador: curl -H "Accept: application/json" -H "Origin: https://example.com" -d team_size=abc https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx, con tu propio dominio permitido como origen. La respuesta es 422 con un mensaje para team_size y para cada campo obligatorio que falte.
  5. Abre Submissions en el panel. El nuevo envío está ahí con todos los campos que enviaste.

Errores comunes

ProblemaCausa y solución
El formulario nunca se envía tras un error del servidorNunca se borró un mensaje de validez personalizado. Restablécelo con setCustomValidity("") en input
minlength no salta con valores rellenados de antemanotooShort solo se aplica después de que la persona edite el campo. La regla del servidor lo detecta
Un pattern se ignora por completoNo es válido con el flag v, y los navegadores se saltan por completo un patrón no válido. Escapa - y otros caracteres especiales dentro de las clases
Los errores se muestran antes de que nadie escribaEl CSS usa :invalid. Cambia a :user-invalid
422 validation_failedEl envío no coincide con la definición de campos del formulario. error.fields indica cada campo y el motivo. Haz que el name del input coincida con el id del campo.

El flujo completo de un envío está en el resumen, y Errores lista todos los códigos que puede recibir un envío.

Preguntas

¿Te atascaste en un paso o quieres una guía para tu caso? Escribe a hello@ikiform.com.