HTML フォームの送信2 分で読めます
ブラウザーとサーバーでの HTML フォームのバリデーション
required、type、pattern、min でフォームを検証し、:user-invalid でエラーをスタイリングし、同じルールをフィールド定義でサーバー側にも適用します。
required、type="email"、min、max、pattern など HTML 組み込みの制約を使えば、何か送られる前にブラウザーが不正な入力を止めます。そのうえで同じルールをサーバーでも適用してください。ボットやスクリプトはブラウザーを完全に素通りするからです。Ikiform ではフォームのフィールドを定義すれば、すべての送信がチェックされます。不正な送信にはフィールドごとのメッセージ付きで 422 validation_failed が返り、setCustomValidity で表示できます。
例はワークショップの予約フォームです。上限のあるチーム人数、過去にできない日付、形式が 1 つの電話番号、決まった形の予約コードと、バリデーションが役に立つ種類のフィールドがそろっています。
ここが違う
バリデーターは 2 つあり、それぞれが見るリクエストは同じではありません。ブラウザーがチェックするのは、スクリプトの有無にかかわらず、あなたのページを使う人だけです。サーバーは、エンドポイントに直接送信するボットや、ブラウザーのチェックをスキップする form.submit() を呼ぶ自分のコードも含め、すべてをチェックします。大事なのは、両方に同じことを言わせることです。
- 人がフォームを送信すると、ブラウザーが HTML の制約をチェックします。不正なフィールドにはメッセージが表示され、何も送られません。
- 正しい送信は Ikiform に届き、フォームのフィールド定義と照合されて、型付きの値として保存されます。
- ボットやスクリプトはエンドポイントに直接送信するので、ブラウザーのチェックは実行されません。
- Ikiform は同じ定義と照合し、何も保存する前に 422 で拒否します。
ブラウザーの層
プレビュー
Ikiform のバリデーターをブラウザーで実行します。何も送信されません。
ws-12 のようなコードを試してみてください。<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 の状態 | 注意 |
|---|---|---|
required | valueMissing | ラジオグループでは、1 つのラジオに required を付ければグループ全体に効きます |
type="email", type="url" | typeMismatch | 緩い: ブラウザーでは a@b がメールアドレスとして通ります |
pattern | patternMismatch | 値全体と一致する必要があります。空の任意フィールドでは無視されます |
minlength, maxlength | tooShort, tooLong | 人が値を編集したあとにだけチェックされます |
min, max | rangeUnderflow, rangeOverflow | 数値、日付、時刻 |
step | stepMismatch | step="1" は小数を拒否します |
| 解析できない入力 | badInput | 一部のブラウザーで数値フィールドに入力された文字 |
setCustomValidity() | customError | 設定した任意のメッセージ。空に戻すまで続きます |
2 つのエンジンでのパターン
ブラウザーは pattern を v フラグでコンパイルします。これは多くの人が書く正規表現より厳格で、文字クラス内のリテラルの - は [a-z\-] のようにエスケープが必要です。Ikiform はフィールドの pattern を u フラグでコンパイルし、やはり値全体と照合します。上の予約コードのように両方で有効なパターンを書き、公開前にフォームのプレビューで 1 つずつテストしてください。
エラーのスタイリング
:invalid はページを読み込んだ瞬間に空の必須フィールドにマッチするので、誰も入力しないうちからすべてのフィールドが赤く表示されます。:user-invalid は、人がフィールドを操作したか送信しようとしたあとにだけマッチします。ほとんどの場合、望ましいのはこちらです:
<style>
#workshop :user-invalid {
border-color: #b42318;
}
</style>サーバーの層
上の定義をフォームの Fields タブに貼り付けます。以降、Ikiform は送信元にかかわらずすべての送信をこの定義で検証し、変換した値を保存します: team_size は数値、phone は区切り文字なし、date は YYYY-MM-DD です。ルールに違反した送信は拒否され、何も保存されず、どのフィールドが失敗したかが返されます:
| フィールド | 送信した値 | メッセージ |
|---|---|---|
team_size | 25 | Must be at most 20 |
team_size | 2.5 | Enter a whole number |
date | 2026-10-30 | Must be on or after 2026-11-02 |
phone | 415 555 0123 | Use international format, e.g. +14155550123 |
code | ws-12 | Codes look like WS-1234 |
website | 任意の値 | Unknown field "website" |
最後の行が重要です。フィールドを定義すると、Ikiform は定義にないフィールドをすべて拒否するので、名前を変えた入力欄は間違った名前で保存されずに、はっきりと失敗します。_ で始まるフィールドとハニーポットは数に入りません。すべての型とルールはフィールドとバリデーションにあります。
プレーンな送信の場合
コードは不要です。拒否されたプレーンな送信では、422 ステータスで Ikiform のホストされたエラーページが表示され、各問題がフィールドのラベルの下に並び、送信元のページへのリンクが付きます。ブラウザーの Back(戻る)ボタンなら入力内容はたいてい残ります。リンクからだとフォームは空の状態で再読み込みされます。
サーバーのエラーをフィールドに表示する
スクリプトを使う場合は、サーバーの各メッセージをブラウザー自身のエラー UI に渡します。setCustomValidity はそのテキストでフィールドを不正としてマークし、reportValidity はいつもの吹き出しで表示して、最初のフィールドにフォーカスします:
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 を実行します。同じフィールドのルールとメッセージをブラウザーで適用します:
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 から読めます。動作を確認する
- フォームの Allowed domains に自分のサイトのドメインを追加し、開発中は
localhostも追加します。空のままにすると、どこからの送信も受け付けます。 - 空のフォームを送信します。ブラウザーが Your name で止め、Network パネルには何も表示されません。
- 開発者ツールでチーム人数の
maxを削除し、25を入力して送信します。プレーンな送信では Ikiform のエラーページが表示され、スクリプト版ではフィールドに Must be at most 20 が表示されます。 - ブラウザーを通さず 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が返ります。 - ダッシュボードで Submissions を開きます。送ったすべてのフィールドを含む新しい送信データがあります。
ハマりどころ
| 問題 | 原因と対処 |
|---|---|
| サーバーのエラーのあと、フォームが一切送信されない | カスタムの validity メッセージがクリアされていません。input で setCustomValidity("") を呼んでリセットしてください |
事前入力された値で minlength が働かない | tooShort は人がフィールドを編集したあとにだけ適用されます。サーバーのルールが捕まえます |
pattern がまったく無視される | v フラグでは不正なパターンで、ブラウザーは不正なパターンを丸ごとスキップします。クラス内の - などの特殊文字をエスケープしてください |
| 入力前からエラーが表示される | CSS が :invalid を使っています。:user-invalid に切り替えてください |
422 validation_failed | 送信がフォームのフィールド定義に合っていません。error.fields に各フィールドと理由が示されます。入力欄の name をフィールド ID に合わせてください。 |
送信の流れ全体は概要にあり、送信に返りうるすべてのコードはエラーに一覧があります。
よくある質問
いいえ。それが動くのはあなたのページを開いたブラウザーの中だけで、ボットやスクリプトはエンドポイントに直接送信します。サーバーでも検証してください。Ikiform ではフォームのフィールドを定義すれば、すべての送信がチェックされます。
input.setCustomValidity(message) を呼んでから form.reportValidity() を呼ぶと、ブラウザーのいつもの吹き出しにメッセージが表示されます。人がフィールドを編集したら setCustomValidity("") でクリアしてください。
:invalid は、ページ読み込み時の空の必須フィールドも含め、制約を満たさないすべてのフィールドにマッチします。:user-invalid は、人がフィールドを操作したか送信しようとしたあとにだけマッチします。
何も保存せずに 422 validation_failed を返します。JSON のリクエストには、各フィールド ID をそのメッセージに対応付ける error.fields が返ります。プレーンなフォーム送信には、フィールドのラベルごとに一覧にしたホストされたエラーページが返ります。
手順で詰まったときや、あなたの状況に合うガイドがほしいときはメールでどうぞ: hello@ikiform.com