Absenden von HTML-Formularen

Absenden von HTML-Formularen6 Min. Lesezeit

HTML-Formularvalidierung im Browser und auf dem Server

Validiere ein Formular mit required, type, pattern und min, gestalte Fehler mit :user-invalid und setze dieselben Regeln mit Felddefinitionen auf dem Server durch.

Preet SutharGründer, Ikiform

Nutze die eingebauten Constraints von HTML wie required, type="email", min, max und pattern, damit der Browser falsche Eingaben blockiert, bevor etwas gesendet wird. Setze dann dieselben Regeln auf dem Server durch, denn Bots und Skripte umgehen den Browser komplett. Definiere bei Ikiform die Felder des Formulars, und jeder Post wird geprüft: Ein fehlerhafter bekommt 422 validation_failed mit einer Meldung pro Feld, die du mit setCustomValidity anzeigen kannst.

Das Beispiel ist ein Buchungsformular für einen Workshop. Es hat die Art Felder, bei denen sich Validierung lohnt: eine Teamgröße mit Grenzen, ein Datum, das nicht in der Vergangenheit liegen darf, eine Telefonnummer in einem Format und ein Buchungscode mit fester Form.

Was hier anders ist

Es gibt zwei Validierer, und sie sehen nicht dieselben Requests. Der Browser prüft nur Menschen, die deine Seite nutzen, mit oder ohne Skripte. Der Server prüft alles, auch Bots, die direkt an den Endpunkt senden, und deinen eigenen Code, der form.submit() aufruft und damit die Prüfungen des Browsers überspringt. Die Arbeit besteht darin, beide dasselbe sagen zu lassen.

Ein Post, der die Browservalidierung und dann die Feldvalidierung von Ikiform durchläufteine Person auf deiner Seiteein Bot oder ein SkriptabsendenBrowser-ConstraintsAttributerequired, type,min, patternIkiform-FelderdieselbenRegeln,durchgesetztgespeicherttypisierteWerteBlase amFeldnichtsgesendetdirekterPOSTkeine Seite,keine PrüfungIkiform-Felder422validation_failednichtsgespeichertungültigEin Post, der die Browservalidierung und dann die Feldvalidierung von Ikiform durchläufteine Person auf deiner Seiteein Bot oder ein SkriptabsendenBrowser-ConstraintsAttribute required,type, min, patternIkiform-Felderdieselben Regeln,durchgesetztgespeicherttypisierte WerteBlase amFeldnichtsgesendetdirekter POSTkeine Seite, keinePrüfungIkiform-Felder422validation_failednichts gespeichertungültig
  1. Eine Person sendet das Formular ab, und der Browser prüft die Constraints im HTML. Ein ungültiges Feld bekommt eine Meldung, und nichts wird gesendet.
  2. Ein gültiger Post erreicht Ikiform, das ihn gegen die Felddefinition des Formulars prüft und typisierte Werte speichert.
  3. Ein Bot oder Skript sendet direkt an den Endpunkt, also läuft keine Browserprüfung.
  4. Ikiform prüft ihn gegen dieselbe Definition und lehnt ihn mit 422 ab, bevor etwas gespeichert wird.
Die Browserschicht ist für Menschen. Die Serverschicht ist die, die hält.

Die Browserschicht

Vorschau

Führt den Validator von Ikiform in deinem Browser aus. Es wird nichts gesendet.

Die Definition, die Ikiform durchsetzt. Probier eine Teamgröße von 25, ein Datum vor dem 2. November oder einen Code wie 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>

Jedes Attribut entspricht einem Gültigkeitsstatus, den du in der Konsole aus input.validity lesen kannst:

AttributGültigkeitsstatusHinweise
requiredvalueMissingBei einer Radio-Gruppe deckt ein Radio mit required die ganze Gruppe ab
type="email", type="url"typeMismatchLocker: a@b gilt in Browsern als E-Mail
patternpatternMismatchMuss auf den ganzen Wert passen. Wird bei einem leeren optionalen Feld ignoriert
minlength, maxlengthtooShort, tooLongWird erst geprüft, nachdem die Person den Wert bearbeitet hat
min, maxrangeUnderflow, rangeOverflowZahlen, Datumsangaben und Uhrzeiten
stepstepMismatchstep="1" lehnt Dezimalzahlen ab
Nicht parsebare EingabebadInputIn manchen Browsern Buchstaben in einem Zahlenfeld
setCustomValidity()customErrorJede Meldung, die du setzt, bis du sie wieder auf leer setzt

Patterns in zwei Engines

Browser kompilieren pattern mit dem Flag v, das strenger ist als die regulären Ausdrücke, die die meisten schreiben: Ein literales - in einer Zeichenklasse muss maskiert werden, wie in [a-z\-]. Ikiform kompiliert das pattern eines Feldes mit dem Flag u und prüft ebenfalls den ganzen Wert. Schreib Patterns, die in beiden gültig sind, wie der Buchungscode oben, und teste jedes in der Formularvorschau, bevor du es veröffentlichst.

Fehler gestalten

:invalid trifft ein leeres Pflichtfeld schon beim Laden der Seite, also zeigt jedes Feld Rot, bevor jemand tippt. :user-invalid trifft erst, nachdem die Person mit dem Feld interagiert oder das Absenden versucht hat, und das ist fast immer, was du willst:

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

Die Serverschicht

Füge die Definition oben im Tab Fields des Formulars ein. Ab dann validiert Ikiform jeden Post dagegen, aus jeder Quelle, und speichert umgewandelte Werte: team_size als Zahl, phone ohne Trennzeichen, date als YYYY-MM-DD. Ein Post, der eine Regel verletzt, wird abgelehnt, nichts wird gespeichert, und die Antwort sagt, welches Feld gescheitert ist:

FeldGesendetNachricht
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
websiteBeliebiger WertUnknown field "website"

Die letzte Zeile ist wichtig. Mit definierten Feldern lehnt Ikiform jedes Feld ab, das nicht in der Definition steht, sodass ein umbenannter Input laut scheitert, statt unter dem falschen Namen gespeichert zu werden. Felder, die mit _ beginnen, und der Honeypot zählen nicht. Jeder Typ und jede Regel steht in Felder und Validierung.

Mit einem einfachen Post

Kein Code nötig. Ein abgelehnter einfacher Post zeigt Ikiforms gehostete Fehlerseite mit dem Status 422, jedes Problem unter dem Label deines Feldes und einen Link zur Seite, von der der Post kam. Der Zurück-Button des Browsers behält meist, was die Person eingetippt hat; der Link lädt das Formular leer neu.

Serverfehler an den Feldern anzeigen

Mit einem Skript speist du jede Servermeldung in die eigene Fehleranzeige des Browsers ein. setCustomValidity markiert das Feld mit diesem Text als ungültig, und reportValidity zeigt sie als die übliche Blase und fokussiert die erste:

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);
});
  • Lösche die eigene Meldung bei input. Ein Feld mit einem eigenen Fehler bleibt ungültig, bis du ihn wieder auf einen leeren String setzt, und bis dahin kann die Person gar nicht absenden.
  • Die Prüfungen des Browsers laufen weiterhin vor dem submit-Event, also sieht der Server nur Posts, die sie bestanden haben.
  • Hat der Fehler keine fields, etwa bei 429 rate_limited, gibt reportValidity() true zurück, und das Skript fällt auf die allgemeine Meldung zurück.

Serverregeln vor dem Senden prüfen

Um reine Serverregeln wie eine Liste von E-Mail-Domains schon vor dem Request zu erwischen, lade die öffentliche Definition des Formulars und führe validate aus dem SDK aus, das dieselben Feldregeln und Meldungen im Browser anwendet:

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();
});
Betrachte das als Komfort. Der Server bleibt die maßgebliche Instanz, und getSchema legt nur Felddefinitionen offen, die ohnehin öffentlich sind: Jeder kann sie unter /f/<formId>/schema lesen.

Prüfen, ob es funktioniert

  1. Füge unter Allowed domains des Formulars die Domain deiner Site hinzu, plus localhost, während du entwickelst. Lass das Feld leer, um Posts von überall anzunehmen.
  2. Sende das leere Formular ab. Der Browser stoppt es bei Your name, und im Network-Panel erscheint nichts.
  3. Entferne max von der Teamgröße in den Entwicklertools, gib 25 ein und sende ab. Der einfache Post zeigt Ikiforms Fehlerseite; der per Skript zeigt Must be at most 20 am Feld.
  4. Sende mit curl und umgeh den Browser: curl -H "Accept: application/json" -H "Origin: https://example.com" -d team_size=abc https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx, mit deiner eigenen erlaubten Domain als Origin. Die Antwort ist 422 mit einer Meldung für team_size und jedes fehlende Pflichtfeld.
  5. Öffne Submissions im Dashboard. Die neue Einsendung ist da, mit jedem Feld, das du gesendet hast.

Stolperfallen

ProblemUrsache und Lösung
Das Formular sendet nach einem Serverfehler nie wieder abEine eigene Gültigkeitsmeldung wurde nie gelöscht. Setze sie bei input mit setCustomValidity("") zurück
minlength greift bei vorausgefüllten Werten nichttooShort gilt erst, nachdem die Person das Feld bearbeitet hat. Die Serverregel fängt es ab
Ein pattern wird komplett ignoriertEs ist mit dem Flag v ungültig, und Browser überspringen ein ungültiges Pattern komplett. Maskiere - und andere Sonderzeichen in Klassen
Fehler erscheinen, bevor jemand tipptDas CSS nutzt :invalid. Wechsle zu :user-invalid
422 validation_failedDer Post passt nicht zur Felddefinition des Formulars. error.fields nennt jedes Feld und den Grund. Gleiche den name des Eingabefelds an die Feld-ID an.

Den vollständigen Ablauf findest du in der Übersicht, und Fehler listet jeden Code, den ein Post zurückbekommen kann.

Fragen

Hängst du bei einem Schritt fest oder willst eine Anleitung für deinen Fall? Schreib an hello@ikiform.com.