HTML フォームの送信

HTML フォームの送信2 分で読めます

ブラウザーとサーバーでの HTML フォームのバリデーション

required、type、pattern、min でフォームを検証し、:user-invalid でエラーをスタイリングし、同じルールをフィールド定義でサーバー側にも適用します。

Preet SutharIkiform 創業者

required、type="email"、min、max、pattern など HTML 組み込みの制約を使えば、何か送られる前にブラウザーが不正な入力を止めます。そのうえで同じルールをサーバーでも適用してください。ボットやスクリプトはブラウザーを完全に素通りするからです。Ikiform ではフォームのフィールドを定義すれば、すべての送信がチェックされます。不正な送信にはフィールドごとのメッセージ付きで 422 validation_failed が返り、setCustomValidity で表示できます。

例はワークショップの予約フォームです。上限のあるチーム人数、過去にできない日付、形式が 1 つの電話番号、決まった形の予約コードと、バリデーションが役に立つ種類のフィールドがそろっています。

ここが違う

バリデーターは 2 つあり、それぞれが見るリクエストは同じではありません。ブラウザーがチェックするのは、スクリプトの有無にかかわらず、あなたのページを使う人だけです。サーバーは、エンドポイントに直接送信するボットや、ブラウザーのチェックをスキップする form.submit() を呼ぶ自分のコードも含め、すべてをチェックします。大事なのは、両方に同じことを言わせることです。

ブラウザーのバリデーションを通り、次に Ikiform のフィールドのバリデーションを通る送信ページを使う人ボットやスクリプト送信ブラウザーの制約required、type、min、patternIkiformのフィールド同じルールを強制保存型付きの値フィールドに吹き出し何も送られない直接 POSTページなし、チェックなしIkiformのフィールド422validation_failed何も保存されない無効ブラウザーのバリデーションを通り、次に Ikiform のフィールドのバリデーションを通る送信ページを使う人ボットやスクリプト送信ブラウザーの制約required、type、min、patternIkiform のフィールド同じルールを強制保存型付きの値フィールドに吹き出し何も送られない直接 POSTページなし、チェックなしIkiform のフィールド422validation_failed何も保存されない無効
  1. 人がフォームを送信すると、ブラウザーが HTML の制約をチェックします。不正なフィールドにはメッセージが表示され、何も送られません。
  2. 正しい送信は Ikiform に届き、フォームのフィールド定義と照合されて、型付きの値として保存されます。
  3. ボットやスクリプトはエンドポイントに直接送信するので、ブラウザーのチェックは実行されません。
  4. Ikiform は同じ定義と照合し、何も保存する前に 422 で拒否します。
ブラウザーの層は人のためのものです。守りを固めるのはサーバーの層です。

ブラウザーの層

プレビュー

Ikiform のバリデーターをブラウザーで実行します。何も送信されません。

Ikiform が適用する定義です。チーム人数に 25、11 月 2 日より前の日付、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>

各属性は 1 つの validity の状態に対応し、コンソールで input.validity から読み取れます:

属性validity の状態注意
requiredvalueMissingラジオグループでは、1 つのラジオに required を付ければグループ全体に効きます
type="email", type="url"typeMismatch緩い: ブラウザーでは a@b がメールアドレスとして通ります
patternpatternMismatch値全体と一致する必要があります。空の任意フィールドでは無視されます
minlength, maxlengthtooShort, tooLong人が値を編集したあとにだけチェックされます
min, maxrangeUnderflow, rangeOverflow数値、日付、時刻
stepstepMismatchstep="1" は小数を拒否します
解析できない入力badInput一部のブラウザーで数値フィールドに入力された文字
setCustomValidity()customError設定した任意のメッセージ。空に戻すまで続きます

2 つのエンジンでのパターン

ブラウザーは pattern を v フラグでコンパイルします。これは多くの人が書く正規表現より厳格で、文字クラス内のリテラルの - は [a-z\-] のようにエスケープが必要です。Ikiform はフィールドの pattern を u フラグでコンパイルし、やはり値全体と照合します。上の予約コードのように両方で有効なパターンを書き、公開前にフォームのプレビューで 1 つずつテストしてください。

エラーのスタイリング

:invalid はページを読み込んだ瞬間に空の必須フィールドにマッチするので、誰も入力しないうちからすべてのフィールドが赤く表示されます。:user-invalid は、人がフィールドを操作したか送信しようとしたあとにだけマッチします。ほとんどの場合、望ましいのはこちらです:

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

サーバーの層

上の定義をフォームの Fields タブに貼り付けます。以降、Ikiform は送信元にかかわらずすべての送信をこの定義で検証し、変換した値を保存します: team_size は数値、phone は区切り文字なし、date は YYYY-MM-DD です。ルールに違反した送信は拒否され、何も保存されず、どのフィールドが失敗したかが返されます:

フィールド送信した値メッセージ
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
website任意の値Unknown field "website"

最後の行が重要です。フィールドを定義すると、Ikiform は定義にないフィールドをすべて拒否するので、名前を変えた入力欄は間違った名前で保存されずに、はっきりと失敗します。_ で始まるフィールドとハニーポットは数に入りません。すべての型とルールはフィールドとバリデーションにあります。

プレーンな送信の場合

コードは不要です。拒否されたプレーンな送信では、422 ステータスで Ikiform のホストされたエラーページが表示され、各問題がフィールドのラベルの下に並び、送信元のページへのリンクが付きます。ブラウザーの Back(戻る)ボタンなら入力内容はたいてい残ります。リンクからだとフォームは空の状態で再読み込みされます。

サーバーのエラーをフィールドに表示する

スクリプトを使う場合は、サーバーの各メッセージをブラウザー自身のエラー UI に渡します。setCustomValidity はそのテキストでフィールドを不正としてマークし、reportValidity はいつもの吹き出しで表示して、最初のフィールドにフォーカスします:

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);
});
  • input でカスタムメッセージをクリアしてください。カスタムエラーのあるフィールドは空文字列に戻すまで不正なままで、そうしないと送信がまったくできなくなります。
  • ブラウザーのチェックは submit イベントの前に実行されるので、サーバーに届くのはそれを通過した送信だけです。
  • 429 rate_limited のようにエラーに fields がない場合、reportValidity() は true を返し、スクリプトは全体向けのメッセージにフォールバックします。

送信前にサーバーのルールをチェックする

メールドメインのリストのようなサーバーだけのルールをリクエスト前に捕まえるには、フォームの公開定義を読み込んで SDK の validate を実行します。同じフィールドのルールとメッセージをブラウザーで適用します:

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();
});
これは利便性のためのものと考えてください。正しさの基準はあくまでサーバーです。getSchema が公開するのはフィールド定義だけで、それはもともと公開情報です。誰でも /f/<formId>/schema から読めます。

動作を確認する

  1. フォームの Allowed domains に自分のサイトのドメインを追加し、開発中は localhost も追加します。空のままにすると、どこからの送信も受け付けます。
  2. 空のフォームを送信します。ブラウザーが Your name で止め、Network パネルには何も表示されません。
  3. 開発者ツールでチーム人数の max を削除し、25 を入力して送信します。プレーンな送信では Ikiform のエラーページが表示され、スクリプト版ではフィールドに Must be at most 20 が表示されます。
  4. ブラウザーを通さず curl で送信します: curl -H "Accept: application/json" -H "Origin: https://example.com" -d team_size=abc https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx。origin には自分の許可ドメインを指定します。team_size と、欠けているすべての必須フィールドについてのメッセージ付きで 422 が返ります。
  5. ダッシュボードで Submissions を開きます。送ったすべてのフィールドを含む新しい送信データがあります。

ハマりどころ

問題原因と対処
サーバーのエラーのあと、フォームが一切送信されないカスタムの validity メッセージがクリアされていません。input で setCustomValidity("") を呼んでリセットしてください
事前入力された値で minlength が働かないtooShort は人がフィールドを編集したあとにだけ適用されます。サーバーのルールが捕まえます
pattern がまったく無視されるv フラグでは不正なパターンで、ブラウザーは不正なパターンを丸ごとスキップします。クラス内の - などの特殊文字をエスケープしてください
入力前からエラーが表示されるCSS が :invalid を使っています。:user-invalid に切り替えてください
422 validation_failed送信がフォームのフィールド定義に合っていません。error.fields に各フィールドと理由が示されます。入力欄の name をフィールド ID に合わせてください。

送信の流れ全体は概要にあり、送信に返りうるすべてのコードはエラーに一覧があります。

よくある質問

手順で詰まったときや、あなたの状況に合うガイドがほしいときはメールでどうぞ: hello@ikiform.com