問い合わせフォームを作る
フォーム送信 API ガイド
管理画面で定義したフォームに、fetch や XHR から送信する方法です。 追加の npm パッケージや API キーは不要で、バリデーション結果を JSON で受け取り、ページ内にエラーを表示できます。
HTML スニペットをそのまま貼る手順は フォーム設置ガイド を参照してください。
1. いつ fetch を使うか
| 方式 | 向いているケース |
|---|---|
HTML <form method="POST"> | 静的サイトにコピペするだけで完結させたい。送信後はリダイレクトや完了メッセージで十分。 |
fetch / XHR | 送信前後にページ遷移させたくない。422 のフィールド別エラーをインライン表示したい。 React / Vue など SPA から送りたい。 |
2. 前提
- 管理画面の
/dashboard/formsでフォームを作成し、ステータスを 公開(受付中) にします。 - フォーム編集画面の URL 末尾(例:
d9slrm8s8iag008o91v0)がformIdです。 /dashboard/forms/mail-settingsの 許可オリジン に、フォームを設置する ページのオリジン(例:https://example.com)を追加します。詳細は フォーム設置ガイド を参照してください。
3. エンドポイント
ベース URL: https://presto.pw
認証: 不要(許可オリジンで保護)
CORS: /f/* は任意オリジンから POST できます。
| Method | Path | 用途 |
|---|---|---|
| GET | https://api.presto.pw/api/health | フォーム受付の readiness 確認(後述) |
| POST | /f/:formId | フォーム送信(バリデーション・保存・通知メール送信) |
4. 送信前のサーバー状態確認
臨時メンテナンスや障害時に、送信ボタンを押す前に「受付停止中」の画面を出したい場合は、 送信前に GET https://api.presto.pw/api/health を呼んでください。認証不要で、任意オリジンから fetch できます。
| HTTP | レスポンス | 意味 |
|---|---|---|
| 200 | { "status": "ok" } | Worker と Control DB が応答可能(フォーム送信を試せる状態) |
| 503 | { "status": "unavailable" } | 受付不可。メンテナンス画面などを表示してください |
| (ネットワーク失敗) | — | Worker 自体に到達できない。503 と同様に停止 UI を表示してください |
呼び出し頻度: ページ表示時または送信直前に 1 回で十分です。数秒おきのポーリングは 無料枠を消費するため避けてください。結果はクライアント側で 30〜60 秒程度キャッシュしても構いません。
const HEALTH_URL = 'https://api.presto.pw/api/health'
const HEALTH_CACHE_MS = 60_000
let healthCheckedAt = 0
let healthReady = false
async function isFormServiceReady() {
const now = Date.now()
if (now - healthCheckedAt < HEALTH_CACHE_MS) {
return healthReady
}
try {
const res = await fetch(HEALTH_URL)
healthReady = res.ok && (await res.json()).status === 'ok'
} catch {
healthReady = false
}
healthCheckedAt = now
return healthReady
}
// ページ表示時: 停止中ならフォームを隠す
const ready = await isFormServiceReady()
if (!ready) {
document.querySelector('#contact-form')?.replaceWith(
document.createTextNode('現在お問い合わせを受け付けておりません。'),
)
}
// 送信直前: 再チェックしてから POST
form.addEventListener('submit', async (event) => {
event.preventDefault()
if (!(await isFormServiceReady())) {
errorBox.textContent = '現在お問い合わせを受け付けておりません。'
return
}
// ... 通常の fetch POST ...
})5. リクエスト
次のいずれかの Content-Type で送れます。フィールド名は管理画面で定義した name と一致させてください。
| 形式 | Content-Type | 備考 |
|---|---|---|
FormData | multipart/form-data(ブラウザが自動付与) | fetch で body: new FormData(form) を使う一般的な方法 |
| URL エンコード | application/x-www-form-urlencoded | 通常の HTML フォーム POST と同じ |
| JSON | application/json | checkbox_group など複数値は配列(例: "interests": ["a", "b"]) |
スパム対策用の ハニーポット フィールド
_hp を必ず含めてください(埋め込みスニペットに同梱)。人間は空のまま、ボットが入力すると 成功レスポンスを返しますがデータは保存されません。JSON レスポンスを受け取るには、リクエストに Accept: application/json ヘッダーを付けてください。
6. レスポンス
| HTTP | message | 内容 |
|---|---|---|
| 200 | (なし) | 成功。Accept: application/json 時は { "success": true } |
| 403 | forbidden | 許可オリジン外からの送信 |
| 404 | not_found | フォーム ID が無効、またはフォームが停止中 |
| 422 | validation_error | バリデーション失敗。errors 配列にフィールド別の理由が入る |
422 レスポンス例
{
"message": "validation_error",
"errors": [
{ "field": "name", "message": "required" },
{ "field": "email", "message": "invalid_email" }
]
}バリデーション message コード
| コード | 意味 |
|---|---|
required | 必須項目が空 |
invalid_email | メール形式が不正 |
invalid_url | URL 形式が不正 |
invalid_number | 数値に変換できない |
too_short / too_long | 文字数が minLength / maxLength の範囲外 |
too_small / too_large | 数値が min / max の範囲外 |
pattern_mismatch | 正規表現パターン不一致 |
invalid_option | select / radio / checkbox_group の選択肢が定義外 |
invalid | その他の形式エラー |
表示文言はクライアント側で message コードから日本語に変換してください。
7. curl で送信する
# FormData 相当(multipart)
curl -X POST "https://presto.pw/f/FORM_ID" \
-H "Accept: application/json" \
-F "name=山田太郎" \
-F "email=yamada@example.com" \
-F "_hp="
# JSON
curl -X POST "https://presto.pw/f/FORM_ID" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{"name":"山田太郎","email":"yamada@example.com","_hp":""}'8. fetch で送信する
フォーム要素から FormData を組み立て、422 時にフィールド別エラーを表示する 基本例です。novalidate を付けると、ブラウザのネイティブ検証とサーバー検証の 二重表示を避けられます。
const form = document.querySelector('#contact-form')
const errorBox = document.querySelector('#form-errors')
form.addEventListener('submit', async (event) => {
event.preventDefault()
errorBox.textContent = ''
const formData = new FormData(form)
if (!formData.has('_hp')) formData.set('_hp', '')
const res = await fetch(form.action, {
method: 'POST',
headers: { Accept: 'application/json' },
body: formData,
})
if (res.status === 422) {
const { errors } = await res.json()
const messages = {
required: '必須項目です',
invalid_email: 'メールアドレスの形式が正しくありません',
invalid_url: 'URL の形式が正しくありません',
invalid_number: '数値を入力してください',
too_short: '文字数が足りません',
too_long: '文字数が多すぎます',
too_small: '値が小さすぎます',
too_large: '値が大きすぎます',
pattern_mismatch: '入力形式が正しくありません',
invalid_option: '選択肢が正しくありません',
invalid: '入力内容が正しくありません',
}
errorBox.textContent = errors
.map((e) => `${e.field}: ${messages[e.message] ?? e.message}`)
.join('\n')
return
}
if (!res.ok) {
errorBox.textContent = '送信に失敗しました。時間をおいて再度お試しください。'
return
}
window.location.href = '/thanks'
})9. React から組み込む
送信成功後のリダイレクトや完了メッセージは、管理画面の「確認方法」設定に関わらず クライアント側で実装してください(JSON レスポンスにはリダイレクト URL は含まれません)。
const SUBMIT_URL = 'https://presto.pw/f/FORM_ID'
const ERROR_LABELS: Record<string, string> = {
required: '必須項目です',
invalid_email: 'メールアドレスの形式が正しくありません',
// ... 上記と同様
}
type FieldError = { field: string; message: string }
export function ContactForm() {
const [fieldErrors, setFieldErrors] = useState<Record<string, string>>({})
const [submitError, setSubmitError] = useState('')
const [isSubmitting, setIsSubmitting] = useState(false)
const handleSubmit = async (event: FormEvent<HTMLFormElement>) => {
event.preventDefault()
setFieldErrors({})
setSubmitError('')
setIsSubmitting(true)
const form = event.currentTarget
const formData = new FormData(form)
formData.set('_hp', '')
try {
const res = await fetch(SUBMIT_URL, {
method: 'POST',
headers: { Accept: 'application/json' },
body: formData,
})
if (res.status === 422) {
const body = (await res.json()) as { errors: FieldError[] }
const next: Record<string, string> = {}
for (const err of body.errors) {
next[err.field] = ERROR_LABELS[err.message] ?? err.message
}
setFieldErrors(next)
return
}
if (!res.ok) {
setSubmitError('送信に失敗しました。')
return
}
window.location.href = '/thanks'
} finally {
setIsSubmitting(false)
}
}
return (
<form onSubmit={handleSubmit} noValidate>
<label htmlFor="name">お名前</label>
<input id="name" name="name" />
{fieldErrors.name && <p role="alert">{fieldErrors.name}</p>}
<label htmlFor="email">メールアドレス</label>
<input id="email" name="email" type="email" />
{fieldErrors.email && <p role="alert">{fieldErrors.email}</p>}
<input type="hidden" name="_hp" value="" tabIndex={-1} autoComplete="off" />
{submitError && <p role="alert">{submitError}</p>}
<button type="submit" disabled={isSubmitting}>
{isSubmitting ? '送信中…' : '送信'}
</button>
</form>
)
}10. HTML POST との違い
| 項目 | HTML <form> POST | fetch + Accept: application/json |
|---|---|---|
| 成功時 | 303 リダイレクト、またはプレーンテキストの完了メッセージ | { "success": true }(ページ遷移はクライアント側で実装) |
| バリデーション失敗時 | 422 の JSON ページが表示される(UX 向きでない) | errors 配列をパースしてインライン表示できる |
| JavaScript | 不要 | 必要 |
11. 関連リンク
- フォーム設置ガイド — HTML スニペットの貼り付け・メール設定
- 管理画面 — フォーム一覧