プラグイン不要でWordPressに予約フォームを実装する実践手順を解説。神奈川のWeb制作会社が実案件で得た知見をもとに、カスタム投稿タイプ+REST APIを活用した構築方法を紹介します。
こんな悩み、ありませんか?
「お問い合わせフォームはあるけれど、予約の日時調整がメールで何往復もかかってしまう」「有名な予約プラグインを入れたら、WordPressが重くなってしまった」「プラグインのサポートが突然終了して、更新のたびにヒヤヒヤしている」——こうした悩みを抱えるWeb担当者の方から、弊社にも多くの相談が寄せられます。
プラグインは確かに手軽ですが、機能が多すぎてサイト速度に影響する・カスタマイズの自由度が低い・将来の互換性が不安という三重苦がついて回ります。本記事では、弊社が実際のクライアントサイトで導入した「プラグイン不要の予約システム」の実装手順を、コード例を交えて解説します。読み終えた後には「自分のサイトでも試せる」と感じていただけるはずです。
なぜプラグインではなく自作を選ぶのか
プラグイン依存のリスクを整理する
予約系プラグインの代表格として「Booking Calendar」「Amelia」などがありますが、弊社がクライアントに導入を提案するうえで毎回壁にぶつかる問題があります。
| 観点 | プラグイン導入 | カスタム自作 |
|---|---|---|
| 初期コスト | 無料〜月額3,000円 | 開発工数(1〜3日) |
| ページ速度への影響 | 大きい(JS/CSS追加) | 最小限 |
| デザイン自由度 | 低い(テンプレート依存) | 完全自由 |
| 将来の互換性 | プラグイン次第 | 自社でコントロール可 |
| セキュリティリスク | 脆弱性報告あり | コードを把握済み |
実際に弊社がサポートしたあるクライアント(神奈川県内の整体院)では、人気予約プラグインのメジャーアップデートを機にWordPressのダッシュボード自体にエラーが発生し、2日間サイトが正常に機能しない事態になりました。その復旧対応と同時に、今回ご紹介する自作システムへの移行を実施。結果としてページ読み込み速度が1.4秒改善し、Googleの評価指標(Core Web Vitals)もグリーン圏に入りました。
実装手順:プラグイン不要の予約システムを作る
全体設計の流れ
flowchart TD
A[カスタム投稿タイプ\n'reservation'を作成] --> B[予約フォームを\nfront-end templateに配置]
B --> C[REST APIエンドポイントを\nfunctions.phpに追加]
C --> D[JavaScriptで\n非同期送信処理]
D --> E[管理画面から\n予約一覧を確認]
E --> F[wp_mailで\n自動確認メール送信]STEP 1:カスタム投稿タイプで予約データを管理する
まず functions.php に予約データを格納するカスタム投稿タイプを登録します。プラグインを使わず、WordPressのネイティブ機能だけで実現できるのがポイントです。
// functions.php
function fivenine_register_reservation_cpt() {
$labels = [
'name' => '予約一覧',
'singular_name' => '予約',
'add_new_item' => '新規予約を追加',
'edit_item' => '予約を編集',
'search_items' => '予約を検索',
'not_found' => '予約が見つかりません',
];
register_post_type( 'reservation', [
'labels' => $labels,
'public' => false,
'show_ui' => true,
'show_in_menu' => true,
'menu_icon' => 'dashicons-calendar-alt',
'supports' => [ 'title', 'custom-fields' ],
'show_in_rest' => true,
] );
}
add_action( 'init', 'fivenine_register_reservation_cpt' );
予約日時・担当者・サービス種別などはカスタムフィールドとして保存します。ACFを使っても構いませんが、シンプルに add_post_meta() だけでも十分対応できます。
STEP 2:REST APIエンドポイントを追加する
予約フォームの送信先となるREST APIエンドポイントを登録します。nonceによるセキュリティ検証を必ず組み込んでください。
// functions.php(続き)
function fivenine_register_reservation_api() {
register_rest_route( 'fivenine/v1', '/reservation', [
'methods' => 'POST',
'callback' => 'fivenine_handle_reservation',
'permission_callback' => '__return_true',
] );
}
add_action( 'rest_api_init', 'fivenine_register_reservation_api' );
function fivenine_handle_reservation( WP_REST_Request $request ) {
// nonceの検証
$nonce = $request->get_header( 'X-WP-Nonce' );
if ( ! wp_verify_nonce( $nonce, 'wp_rest' ) ) {
return new WP_Error( 'forbidden', '不正なリクエストです', [ 'status' => 403 ] );
}
$name = sanitize_text_field( $request->get_param( 'name' ) );
$email = sanitize_email( $request->get_param( 'email' ) );
$date = sanitize_text_field( $request->get_param( 'date' ) );
$service = sanitize_text_field( $request->get_param( 'service' ) );
// バリデーション
if ( empty( $name ) || ! is_email( $email ) || empty( $date ) ) {
return new WP_Error( 'invalid_input', '入力内容をご確認ください', [ 'status' => 400 ] );
}
// 投稿として保存
$post_id = wp_insert_post( [
'post_type' => 'reservation',
'post_title' => "{$name} 様 / {$date}",
'post_status' => 'publish',
] );
if ( is_wp_error( $post_id ) ) {
return new WP_Error( 'save_failed', '予約の保存に失敗しました', [ 'status' => 500 ] );
}
update_post_meta( $post_id, '_reservation_email', $email );
update_post_meta( $post_id, '_reservation_date', $date );
update_post_meta( $post_id, '_reservation_service', $service );
// 確認メール送信
wp_mail(
$email,
'【予約確認】ご予約を承りました',
"「{$name}」様\n\n{$date} のご予約を承りました。\nサービス:{$service}\n\n当日はどうぞよろしくお願いいたします。"
);
return rest_ensure_response( [ 'success' => true, 'message' => 'ご予約を受け付けました。' ] );
}
STEP 3:フロントエンドのフォームと非同期送信
<form id="reservation-form">
<label>お名前 <input type="text" name="name" required></label>
<label>メールアドレス <input type="email" name="email" required></label>
<label>ご希望日時 <input type="datetime-local" name="date" required></label>
<label>サービス
<select name="service">
<option value="60min">60分コース</option>
<option value="90min">90分コース</option>
</select>
</label>
<button type="submit">予約する</button>
<p id="reservation-message"></p>
</form>
nonce値をJavaScriptに渡す際は、wp_localize_script() を使うのが定石です。
wp_localize_script( 'your-script-handle', 'fivenineVars', [
'nonce' => wp_create_nonce( 'wp_rest' ),
] );
よくある失敗パターンと対処法
実際の案件で遭遇した「やりがちなミス」を共有します。特に2つ目は痛い経験からの教訓です。
① メール送信の失敗に気づかない
wp_mail() はデフォルトのPHPメール関数を使うため、レンタルサーバーによっては迷惑メールに振り分けられたり、そもそも到達しないことがあります。WP Mail SMTPプラグインでSMTP設定を行うことを強くお勧めします。これだけはプラグインを活用してください。
② バリデーションをフロントだけで済ませてしまう
JavaScriptのバリデーションは「UX向上」のためのもので、セキュリティには機能しません。前述のPHP側コードのように、sanitize_text_field() や is_email() などのWordPressネイティブ関数でサーバーサイドでも必ず検証してください。
③ 予約が重複しても気づかない 単純な実装では同じ日時に複数の予約が入っても弾けません。保存前に同日時の投稿が存在するかチェックするロジックを追加するか、カレンダーUIで選択済みの枠をグレーアウトする処理を組み込みましょう。
④ 管理画面の一覧が見づらい
manage_{post_type}_posts_columns フックを使ってカラムをカスタマイズしないと、タイトルしか見えない一覧になってしまいます。予約日・サービス種別・メールアドレスを列として追加するひと手間が、運用効率を大きく左右します。
実際どう変わったか:整体院クライアントの事例
冒頭で触れた整体院の事例を少し詳しく共有します。元々は「お問い合わせ→日時相談→確定」という3ステップのメールのやり取りが必要で、成約までに平均2〜3日かかっていました。自作予約システムの導入後は、来院を決めた方がその場で日時を確定して送信できるようになり、当日〜翌日の予約確定率が大幅に向上。担当者への問い合わせメールも月換算で約30件増加しました(比較期間:導入前後各3ヶ月)。
システム自体の構築にかかった工数は2日半。プラグインの年間ライセンス費用(約36,000円/年)が不要になり、1年以内に開発コストを回収できる試算です。
サイトの改善、プロに任せませんか?
WordPress制作・リニューアル
更新しやすく、集客につながるサイトを構築します
※ 通常1営業日以内にご返信します
まとめと次のステップ
プラグインに頼らない予約システムは、「難しそう」という印象とは裏腹に、WordPressの標準機能だけで十分実現できます。初期構築にある程度の工数はかかりますが、長期的な保守コストや速度・セキュリティ面での恩恵は大きく、実案件での効果も実証済みです。
まずは自分のサイトでカスタム投稿タイプを登録してみるところから始めてみてください。「ここから先は自信がない」という場合は、お気軽に弊社までご相談ください。要件ヒアリングから実装・テストまで一貫してサポートします。