Envoi de formulaire HTML

Envoi de formulaire HTML7 min de lecture

Validation des formulaires HTML dans le navigateur et sur le serveur

Validez un formulaire avec required, type, pattern et min, stylisez les erreurs avec :user-invalid, et imposez les mêmes règles sur le serveur avec des définitions de champs.

Preet SutharFondateur, Ikiform

Utilisez les contraintes intégrées au HTML, comme required, type="email", min, max et pattern, pour que le navigateur bloque les saisies incorrectes avant tout envoi. Imposez ensuite les mêmes règles sur le serveur, car les bots et les scripts contournent entièrement le navigateur. Avec Ikiform, définissez les champs du formulaire et chaque envoi est vérifié : un envoi incorrect reçoit 422 validation_failed avec un message par champ, que vous pouvez afficher avec setCustomValidity.

L'exemple est un formulaire de réservation d'atelier. Il contient le genre de champs où la validation se rend utile : une taille d'équipe avec des limites, une date qui ne peut pas être passée, un numéro de téléphone dans un seul format et un code de réservation de forme fixe.

Ce qui change ici

Il y a deux validateurs, et ils ne voient pas les mêmes requêtes. Le navigateur ne vérifie que les personnes qui utilisent votre page, avec ou sans scripts. Le serveur vérifie tout, y compris les bots qui envoient directement à l'endpoint et votre propre code qui appelle form.submit(), lequel ignore les vérifications du navigateur. Le travail consiste à faire dire la même chose aux deux.

Un envoi qui passe par la validation du navigateur puis par la validation des champs d'Ikiformune personne sur votre pageun bot ou un scriptenvoicontraintesdunavigateurattributsrequired, type,min, patternchampsIkiformles mêmesrègles,imposéesstockévaleurs typéesbulle sur lechamprien n'estenvoyéPOSTdirectni page, nivérificationschampsIkiform422validation_failedrien n'eststockéinvalideUn envoi qui passe par la validation du navigateur puis par la validation des champs d'Ikiformune personne sur votre pageun bot ou un scriptenvoicontraintes dunavigateurattributs required,type, min, patternchamps Ikiformles mêmes règles,imposéesstockévaleurs typéesbullesur lechamprien n'estenvoyéPOST directni page, nivérificationschamps Ikiform422validation_failedrien n'est stockéinvalide
  1. Une personne envoie le formulaire, et le navigateur vérifie les contraintes du HTML. Un champ invalide reçoit un message et rien n'est envoyé.
  2. Un envoi valide atteint Ikiform, qui le vérifie par rapport à la définition de champs du formulaire et stocke des valeurs typées.
  3. Un bot ou un script envoie directement à l'endpoint, donc aucune vérification du navigateur ne s'exécute.
  4. Ikiform le vérifie par rapport à la même définition et le refuse avec 422 avant que quoi que ce soit ne soit stocké.
Le niveau navigateur est fait pour les personnes. C'est le niveau serveur qui tient.

Le niveau navigateur

Aperçu

Exécute le validateur d'Ikiform dans votre navigateur. Rien n'est envoyé.

La définition qu'impose Ikiform. Essayez une taille d'équipe de 25, une date antérieure au 2 novembre, ou un code comme 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>

Chaque attribut correspond à un état de validité, que vous pouvez lire depuis input.validity dans la console :

AttributÉtat de validitéRemarques
requiredvalueMissingPour un groupe de boutons radio, un seul radio avec required couvre le groupe
type="email", type="url"typeMismatchPermissif : a@b passe pour un e-mail dans les navigateurs
patternpatternMismatchDoit correspondre à toute la valeur. Ignoré sur un champ facultatif vide
minlength, maxlengthtooShort, tooLongVérifié seulement après que la personne a modifié la valeur
min, maxrangeUnderflow, rangeOverflowNombres, dates et heures
stepstepMismatchstep="1" refuse les décimales
Saisie non analysablebadInputDes lettres tapées dans un champ numérique dans certains navigateurs
setCustomValidity()customErrorTout message que vous définissez, jusqu'à ce que vous le remettiez à vide

Des patterns dans deux moteurs

Les navigateurs compilent pattern avec le flag v, plus strict que les expressions régulières que la plupart des gens écrivent : un - littéral dans une classe de caractères doit être échappé, comme dans [a-z\-]. Ikiform compile le pattern d'un champ avec le flag u et le fait aussi correspondre à toute la valeur. Écrivez des patterns valides dans les deux, comme le code de réservation ci-dessus, et testez chacun dans l'aperçu du formulaire avant de le publier.

Styliser les erreurs

:invalid correspond à un champ obligatoire vide dès le chargement de la page, donc tous les champs s'affichent en rouge avant que quiconque ne tape. :user-invalid ne correspond qu'après que la personne a interagi avec le champ ou tenté d'envoyer, ce qui est presque toujours ce que vous voulez :

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

Le niveau serveur

Collez la définition ci-dessus dans l'onglet Fields du formulaire. Dès lors, Ikiform valide chaque envoi par rapport à elle, quelle qu'en soit la source, et stocke des valeurs converties : team_size comme nombre, phone sans séparateurs, date au format YYYY-MM-DD. Un envoi qui enfreint une règle est refusé, rien n'est stocké, et la réponse indique quel champ a échoué :

ChampEnvoyéMessage
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
websiteN'importe quelle valeurUnknown field "website"

La dernière ligne compte. Avec des champs définis, Ikiform refuse tout champ absent de la définition, donc un input renommé échoue de façon visible au lieu d'être stocké sous le mauvais nom. Les champs commençant par _ et le honeypot ne comptent pas. Chaque type et chaque règle figurent dans Champs et validation.

Avec un envoi classique

Aucun code nécessaire. Un envoi classique refusé affiche la page d'erreur hébergée d'Ikiform avec le statut 422, chaque problème sous le libellé de votre champ, et un lien vers la page d'où vient l'envoi. Le bouton Back du navigateur conserve généralement ce que la personne a saisi ; le lien recharge le formulaire vide.

Afficher les erreurs du serveur sur les champs

Avec un script, injectez chaque message du serveur dans l'interface d'erreur propre au navigateur. setCustomValidity marque le champ comme invalide avec ce texte, et reportValidity l'affiche sous forme de bulle habituelle et place le focus sur le premier :

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);
});
  • Effacez le message personnalisé sur input. Un champ avec une erreur personnalisée reste invalide jusqu'à ce que vous la remettiez à une chaîne vide, et la personne ne peut alors plus du tout envoyer.
  • Les vérifications du navigateur s'exécutent toujours avant l'événement submit, donc le serveur ne voit que les envois qui les ont passées.
  • Si l'erreur n'a pas de fields, comme 429 rate_limited, reportValidity() renvoie true et le script se rabat sur le message général.

Vérifier les règles du serveur avant l'envoi

Pour repérer avant la requête les règles propres au serveur, comme une liste de domaines d'e-mail, chargez la définition publique du formulaire et exécutez le validate du SDK, qui applique dans le navigateur les mêmes règles et messages de champ :

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();
});
Considérez cela comme un confort. Le serveur reste la source de vérité, et getSchema n'expose que les définitions de champs, qui sont de toute façon publiques : n'importe qui peut les lire depuis /f/<formId>/schema.

Vérifiez que tout fonctionne

  1. Dans Allowed domains du formulaire, ajoutez le domaine de votre site, plus localhost pendant le développement. Laissez vide pour accepter les envois de partout.
  2. Envoyez le formulaire vide. Le navigateur l'arrête à Votre nom et rien n'apparaît dans le panneau Réseau.
  3. Retirez max de la taille d'équipe dans les outils de développement, saisissez 25 et envoyez. L'envoi classique affiche la page d'erreur d'Ikiform ; l'envoi par script affiche Must be at most 20 sur le champ.
  4. Envoyez avec curl, sans passer par le navigateur : curl -H "Accept: application/json" -H "Origin: https://example.com" -d team_size=abc https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx, avec votre propre domaine autorisé comme origine. La réponse est un 422 avec un message pour team_size et pour chaque champ obligatoire manquant.
  5. Ouvrez Submissions dans le tableau de bord. La nouvelle soumission y est, avec tous les champs envoyés.

Pièges

ProblèmeCause et solution
Le formulaire ne s'envoie plus après une erreur du serveurUn message de validité personnalisé n'a jamais été effacé. Réinitialisez-le avec setCustomValidity("") sur input
minlength ne se déclenche pas sur les valeurs prérempliestooShort ne s'applique qu'après que la personne a modifié le champ. La règle du serveur le rattrape
Un pattern est entièrement ignoréIl est invalide avec le flag v, et les navigateurs ignorent entièrement un pattern invalide. Échappez - et les autres caractères spéciaux dans les classes
Les erreurs s'affichent avant que quiconque ne tapeLe CSS utilise :invalid. Passez à :user-invalid
422 validation_failedL'envoi ne correspond pas à la définition des champs du formulaire. error.fields nomme chaque champ et la raison. Faites correspondre le name de l'input à l'id du champ.

Le déroulement complet d'un envoi se trouve dans la vue d'ensemble, et Erreurs liste chaque code qu'un envoi peut recevoir.

Questions

Une étape vous bloque, ou vous voulez un guide pour votre cas ? Écrivez à hello@ikiform.com.