技術ブログ
【WordPress】Contact Form 7 に確認画面を付ける ── Multi-Step Forms を使わない方法を試してみた
Contact Form 7(以下 CF7)には、入力内容を送信前に見せる確認画面の機能がありません。よく使われるのは Multi-Step Forms プラグインですが、入力ページと確認ページを別々の固定ページとして作る必要があり、フォームが増えると管理の手間が増えます。
本記事では、1つのページの中で「入力 → 確認 → 送信」を切り替える方法を3通り、WordPress 7.1.4/CF7 6.2.4.1 の検証環境で実際に動かしました。おすすめの自作の方法は、コードを1行ずつ説明しています(3章)。PHP や JavaScript に慣れていない方も、上から順に読めば何をしているか分かるように書きました。
① 確認画面用のプラグインを使わずに自作する(PHP・JavaScript・CSS の3ファイルで小さなプラグインを1つ作る → 3章)。CF7 の入力チェック・迷惑メール対策がそのまま効き、クイズ・画像認証とも一緒に使えます
② 手早く済ませたいなら Confirm Plus Contact Form 7(入れるだけ → 4章)。ただし2つの注意点を理解したうえで
③ Contact Form 7 add confirm は使わない(CF7 5.4 以降は確認画面に進めない → 5章)
1. 結論
| ご質問 | 答え |
|---|---|
| Multi-Step Forms 以外に、CF7 で確認画面を作る方法はあるか | あります。いちばんのおすすめは自作です。CF7 が「メールを送る直前」に呼ぶ仕組み(wpcf7_before_send_mail)で送信を止め、その間に確認画面を出します。PHP・JavaScript・CSS の3ファイル(説明のメモを含めて約120行)で作れました。 |
| プラグインで済ませる方法はあるか | あります。Confirm Plus Contact Form 7 は、入れるだけで確認画面が出ました。ただし、JavaScript が動かない環境では「送信されました」と出るのにメールが届かないこと、最終送信では迷惑メールの判定をしない作りであることを確認しました。 |
| 昔からある Contact Form 7 add confirm は使えるか | 使えません。2018年で更新が止まっていて、CF7 5.4(2021年2月)以降は確認ボタンを押しても先に進みません。エラーも出ないので、気づかないまま問い合わせを受け取れなくなります。 |

2. 確認画面の作り方は4通り
| 作り方 | 作るもの | CF7 6.2.4.1 での結果 | 注意 |
|---|---|---|---|
| A. 自作 おすすめ |
PHP・JavaScript・CSS の3ファイルで小さなプラグインを1つ作る | ✅ 入力チェック → 確認 → 戻る → 修正 → 送信まで動作。クイズ・画像認証・問い合わせ保存プラグインとも一緒に使える | 確認画面に出す項目名をフォーム側に書く(data-label) |
| B. Confirm Plus Contact Form 7 2.0.3.1(2026年7月更新) |
入れるだけ | ✅ 確認画面を自動で作る | JavaScript が動かないと偽の「送信完了」になる/最終送信で迷惑メール判定をしない/ボタンの見た目はテーマに合わせて調整が要る |
| C. Contact Form 7 add confirm 5.1(2018年2月で更新停止) |
フォームに [confirm] [back] を足す |
❌ 確認画面に進めない | CF7 5.3.2 では動くことを確認(それより前の版は試していません) |
| D. Multi-Step Forms 4.7.1(2026年10月更新) |
入力ページと確認ページを別の固定ページで作る | (今回は試していません) | ステップの数だけページとフォームが要る |
このほか、確認画面を最初から持つフォームプラグイン(MW WP Form、Snow Monkey Forms など)に乗り換える方法もあります。MW WP Form は公式ページに「このプラグインは現在、必要最低限のメンテナンスリリースのみ提供しています。」と書かれていて、WordPress での動作確認は 6.4 までです。Snow Monkey Forms 13 は WordPress 7.1 以上が必要です(最新の CF7 6.2.4.1 も同じく 7.1 以上が必要です。いずれも2026年11月4日 WordPress.org で確認)。今あるフォームを作り直す手間もかかるため、CF7 のまま確認画面を付ける A か B をおすすめします。
3. 作り方A:確認画面用のプラグインを使わずに自作する ★おすすめ
3-1. 仕組み
ポイントは、「確認」ボタンでも CF7 にふつうに送信させることです。CF7 はいつもどおり入力チェック・同意の確認・迷惑メールの判定を行い、メールを送る直前に wpcf7_before_send_mail というタイミングで、登録されている処理を呼びます。ここで「確認ボタンから来た送信なら止める」と書けば、チェックは全部通したうえで、メールだけ送らない状態が作れます。
[入力画面] 「入力内容を確認する」ボタン(name="_bit_step" value="confirm")
│ CF7 がふつうに送信 → 入力チェック・同意・迷惑メール判定
▼
[サーバ(PHP)] wpcf7_before_send_mail で「確認ボタンから来た」なら送信を止める
結果に bit_confirm という名前を付けて返す
▼
[画面(JavaScript)] CF7 の合図(wpcf7submit)で bit_confirm を受け取ったら、入力欄を隠して確認表を出す
│ 「戻って修正する」→ 入力画面に戻る(入力した値は残っている)
▼ 「送信する」(CF7 のふつうの送信ボタン)→ いつもどおりメールを送る
入力チェックをサーバ(CF7 本体)に任せるので、必須・メール形式・同意・迷惑メール対策が CF7 の設定どおりに効きます。チェックを JavaScript で作り直す必要がありません。

3-2. 先に知っておく言葉
| 言葉 | 意味 |
|---|---|
| フック | WordPress やプラグインが用意している「ここで処理を差し込んでいいですよ」というタイミング。名前が付いている(例:wpcf7_before_send_mail = CF7 がメールを送る直前) |
add_action()add_filter() |
フックに自分の処理を登録する命令。add_filter() は値を受け取って返す処理に使う |
| 優先度 | 同じフックに処理がいくつも登録されているときの順番。数字の小さいほうが先に動く(ふつうは10) |
| イベント(合図) | JavaScript で「何かが起きた」ことを知らせる仕組み。CF7 は送信の結果が返ってくるたびに wpcf7submit という合図を出す |
3-3. ファイルの構成と入れ方
wp-content/plugins/
└─ bit-cf7-confirm/ ← フォルダを作る
├─ bit-cf7-confirm.php ← プラグイン本体(54行)
├─ confirm.js ← 確認表を作り、画面を切り替える(59行)
└─ confirm.css ← 入力画面と確認画面の出し分け(8行)
wp-content/plugins/の中にbit-cf7-confirmというフォルダを作り、下の3つのファイルを置きます。- 管理画面の「プラグイン」に「bit CF7 確認画面(自作の見本)」が出るので、有効化します。
- CF7 の「コンタクトフォーム」→ 確認画面を付けたいフォームの「フォーム」タブを、3-5 の形に書き換えます。
テーマではなくプラグインにしておくと、テーマを替えても確認画面が残ります。また、3-5 の形に書き換えたフォームにだけ効くので、ほかのフォームには影響しません(同じページに2つのフォームを置いても、互いに影響しないことを確かめました)。
3-4. bit-cf7-confirm.php(プラグイン本体)を1行ずつ
まず全体です(コピーして使えます)。
<?php
/*
* Plugin Name: bit CF7 確認画面(自作の見本)
* Description: Contact Form 7 に確認画面を付ける。「確認」ボタンは CF7 の入力チェックだけ通してメールを送らずに止め、確認画面を出す。
* Version: 0.2.0
*/
// 「確認」ボタン(name="_bit_step" value="confirm")で送られたときは、
// 入力チェック・同意・迷惑判定をすべて通った後、メールを送る直前で止める。
add_action( 'wpcf7_before_send_mail', function ( $contact_form, &$abort, $submission ) {
if ( ( $_POST['_bit_step'] ?? '' ) !== 'confirm' ) {
return; // 「送信する」ボタン=通常どおり送る
}
$abort = true;
$submission->set_status( 'bit_confirm' );
$submission->set_response( '入力内容をご確認のうえ「送信する」を押してください。' );
// 同じタイミングで動くほかの処理(問い合わせ保存の CFDB7・決済の Stripe など)を、確認の段階では動かさない
remove_all_actions( 'wpcf7_before_send_mail' );
}, PHP_INT_MIN, 3 ); // いちばん最初に動かす
// クイズ([quiz])と画像認証([captchar])は、確認の段階では答え合わせをしない。
// CF7 は送信の応答のたびに問題を出し直して答えの欄を空にするので、確認の段階で答えさせると最後の送信で必ず不正解になる。
// これらは確認画面の側に置き、最後の「送信する」で答え合わせする。
foreach ( array(
'wpcf7_validate_quiz' => 'wpcf7_quiz_validation_filter',
'wpcf7_validate_captchar' => 'wpcf7_captcha_validation_filter',
) as $hook => $checker ) {
add_filter( $hook, function ( $result ) use ( $hook, $checker ) {
if ( ( $_POST['_bit_step'] ?? '' ) === 'confirm' ) {
remove_filter( $hook, $checker, 10 ); // CF7 の答え合わせ(優先度10)を、この送信のときだけ外す
}
return $result;
}, 1 );
}
// 確認画面を使うフォームの直前に、JavaScript が動かないときの案内を付ける
// (フォームの中に <noscript> を書いても CF7 が枠を消してしまうので、フォームの外に足す)
add_filter( 'do_shortcode_tag', function ( $output, $tag ) {
if ( 'contact-form-7' !== $tag || false === strpos( $output, 'bit-confirm' ) ) {
return $output;
}
return '<noscript><p class="bit-nojs">確認画面の表示には JavaScript が必要です。JavaScript が使えない場合は、お電話またはメールでお問い合わせください。</p></noscript>' . $output;
}, 10, 2 );
add_action( 'wp_enqueue_scripts', function () {
wp_enqueue_script(
'bit-cf7-confirm',
plugins_url( 'confirm.js', __FILE__ ),
array( 'contact-form-7' ),
'0.2.0',
true
);
wp_enqueue_style( 'bit-cf7-confirm', plugins_url( 'confirm.css', __FILE__ ), array(), '0.2.0' );
} );
1行ずつの説明です(空行は省いています)。
| 行 | コード | 何をしているか |
|---|---|---|
| 1 |
<?php |
PHP のファイルの始まりの印です。 |
| 2〜6 |
/* * Plugin Name: bit CF7 確認画面(自作の見本) * Description: Contact Form 7 に確認画面を付ける。「確認」ボタンは CF7 の入力チェックだけ通してメールを送らずに止め、確認画面を出す。 * Version: 0.2.0 */ |
プラグインの名札です。WordPress はファイルの先頭のコメントにある Plugin Name: を読み、管理画面の「プラグイン」一覧にこの名前で表示します。Description は説明文、Version は版の番号です。 |
| 8〜9 |
// 「確認」ボタン(name="_bit_step" value="confirm")で送られたときは、 // 入力チェック・同意・迷惑判定をすべて通った後、メールを送る直前で止める。 |
// で始まる行はメモ(コメント)で、動きには関係しません。以下、コメントの行は同じです。 |
| 10 |
add_action( 'wpcf7_before_send_mail', function ( $contact_form, &$abort, $submission ) {
|
add_action() は「決まったタイミング(フック)に、自分の処理を差し込む」ための WordPress の命令です。wpcf7_before_send_mail は CF7 が用意しているタイミングの名前で、「入力チェックがすべて通り、これからメールを送る」直前を表します。function ( … ) { … } が差し込む処理の本体で、CF7 から3つの値を受け取ります。・ $contact_form:どのフォームからの送信か・ &$abort:「メールを送るのをやめるか」の旗。先頭の & は「ここで書き換えると CF7 にも伝わる」という印です・ $submission:今回の送信の中身と結果 |
| 11 |
if ( ( $_POST['_bit_step'] ?? '' ) !== 'confirm' ) {
|
$_POST は、ブラウザから送られてきた入力の一覧です。$_POST['_bit_step'] は、その中の _bit_step という名前の値で、「入力内容を確認する」ボタンを押したときだけ confirm が入ります(フォームの8行目で設定)。?? '' は「値が無ければ空の文字として扱う」という書き方、!== 'confirm' は「confirm ではないなら」という意味です。 |
| 12〜13 |
return; // 「送信する」ボタン=通常どおり送る
}
|
確認ボタン以外(=「送信する」ボタン)なら、return でここで処理を終えます。何もしないので、CF7 はいつもどおりメールを送ります。 |
| 14 |
$abort = true; |
ここから下は、確認ボタンで送られたときだけ動きます。$abort = true; で「メールを送るのをやめる」旗を立てます。このタイミングに来る時点で、CF7 の入力チェック・同意の確認・迷惑メールの判定はすべて済んでいます。つまり「チェックは全部通したうえで、メールだけ送らない」状態になります。 |
| 15 |
$submission->set_status( 'bit_confirm' ); |
今回の送信の結果に bit_confirm という名前を付けます。画面側の JavaScript(confirm.js の11行目)はこの名前を見て、確認画面に切り替えます。⚠ この名前に使えるのは英小文字・数字・ _(アンダーバー)だけです。bit-confirm のようにハイフンを使うと CF7 が黙って無視し、確認画面が出なくなります。 |
| 16 |
$submission->set_response( '入力内容をご確認のうえ「送信する」を押してください。' ); |
フォームの下に出るメッセージを決めます。 |
| 17〜18 |
// 同じタイミングで動くほかの処理(問い合わせ保存の CFDB7・決済の Stripe など)を、確認の段階では動かさない
remove_all_actions( 'wpcf7_before_send_mail' );
|
remove_all_actions() は、同じタイミングに登録されているほかの処理を、今回の送信に限って全部外す命令です。問い合わせを保存するプラグイン(CFDB7 など)もこのタイミングで保存するため、外さないと「確認」を押しただけで記録が残ってしまいます。 |
| 19 |
}, PHP_INT_MIN, 3 ); // いちばん最初に動かす |
PHP_INT_MIN は「処理の順番(優先度)」です。WordPress は数字の小さい順に処理するので、PHP でいちばん小さい数を指定するといちばん最初に動きます。だから18行目で、後ろに並んでいるほかの処理を外せます。最後の 3 は「受け取る値の数」です。3つと書かないと、最初の1つしか渡されません。 |
| 21〜23 |
// クイズ([quiz])と画像認証([captchar])は、確認の段階では答え合わせをしない。 // CF7 は送信の応答のたびに問題を出し直して答えの欄を空にするので、確認の段階で答えさせると最後の送信で必ず不正解になる。 // これらは確認画面の側に置き、最後の「送信する」で答え合わせする。 |
メモです。クイズと画像認証の扱いを説明しています(下の 3-8 も参照)。 |
| 24〜27 |
foreach ( array(
'wpcf7_validate_quiz' => 'wpcf7_quiz_validation_filter',
'wpcf7_validate_captchar' => 'wpcf7_captcha_validation_filter',
) as $hook => $checker ) {
|
foreach は「一覧の中身を1組ずつ順に処理する」繰り返しです。ここでは次の2組を順に処理します。・ wpcf7_validate_quiz(クイズのチェックのタイミング)と、CF7 本体のクイズの答え合わせ wpcf7_quiz_validation_filter・ wpcf7_validate_captchar(画像認証のチェックのタイミング)と、その答え合わせ wpcf7_captcha_validation_filter1組ずつ、左を $hook、右を $checker という名前で使います。 |
| 28 |
add_filter( $hook, function ( $result ) use ( $hook, $checker ) {
|
add_filter() も、決まったタイミングに処理を差し込む命令です(add_action() との違いは、値を受け取って返すこと)。最後の 1 が優先度で、CF7 の答え合わせ(優先度10)より先に動きます。use ( $hook, $checker ) は、外側の $hook と $checker を中で使えるようにする書き方です。 |
| 29〜31 |
if ( ( $_POST['_bit_step'] ?? '' ) === 'confirm' ) {
remove_filter( $hook, $checker, 10 ); // CF7 の答え合わせ(優先度10)を、この送信のときだけ外す
}
|
確認ボタンで送られたときだけ、remove_filter() で CF7 の答え合わせを外します。こうすると、確認の段階ではクイズや画像認証を判定しません。最後の「送信する」のときは外さないので、そこでちゃんと答え合わせされます。 |
| 32〜34 |
return $result;
}, 1 );
}
|
add_filter() で差し込んだ処理は、受け取った値(チェックの結果)を必ずそのまま返す決まりです。 |
| 36〜37 |
// 確認画面を使うフォームの直前に、JavaScript が動かないときの案内を付ける // (フォームの中に <noscript> を書いても CF7 が枠を消してしまうので、フォームの外に足す) |
メモです。 |
| 38 |
add_filter( 'do_shortcode_tag', function ( $output, $tag ) {
|
do_shortcode_tag は、ショートコード( など)がHTML に変わった直後のタイミングです。$output にできあがった HTML、$tag にショートコードの名前が入ります。 |
| 39〜41 |
if ( 'contact-form-7' !== $tag || false === strpos( $output, 'bit-confirm' ) ) {
return $output;
}
|
CF7 のフォームでない、または確認画面の仕組み(bit-confirm)を使っていないフォームなら、何もせずにそのまま返します。strpos() は「文字が含まれているか」を調べる命令です。 |
| 42 |
return '<noscript><p class="bit-nojs">確認画面の表示には JavaScript が必要です。JavaScript が使えない場合は、お電話またはメールでお問い合わせください。</p></noscript>' . $output; |
フォームの HTML の前に <noscript> の案内を足して返します。<noscript> の中身は、JavaScript が動かないブラウザでだけ表示されます。(フォームの中に <noscript> を書いても、CF7 が枠を消して常に表示してしまうので、フォームの外に足しています) |
| 43 |
}, 10, 2 ); |
優先度10(ふつう)、受け取る値は2つ($output と $tag)です。 |
| 45 |
add_action( 'wp_enqueue_scripts', function () {
|
wp_enqueue_scripts は、ページを表示するときに読み込む JavaScript と CSS を登録するタイミングです。 |
| 46〜52 |
wp_enqueue_script(
'bit-cf7-confirm',
plugins_url( 'confirm.js', __FILE__ ),
array( 'contact-form-7' ),
'0.2.0',
true
);
|
wp_enqueue_script() で confirm.js を読み込みます。中の値は上から順に次のとおりです。・ 'bit-cf7-confirm':この JavaScript の呼び名・ plugins_url( 'confirm.js', __FILE__ ):このプラグインのフォルダにある confirm.js の URL・ array( 'contact-form-7' ):CF7 の JavaScript を先に読み込む指定(CF7 の合図を使うため)・ '0.2.0':版の番号。URL の最後に ?ver=0.2.0 が付くので、ファイルを直したら番号を上げると、閲覧者のブラウザに古いファイルが残りません・ true:ページの最後で読み込む |
| 53〜54 |
wp_enqueue_style( 'bit-cf7-confirm', plugins_url( 'confirm.css', __FILE__ ), array(), '0.2.0' ); } ); |
wp_enqueue_style() で confirm.css を同じように読み込みます。 |
CF7 は送信を受け取ると、① 入力チェック(必須・メール形式など)→ ② 同意の確認 → ③ 迷惑メールの判定 → ④
wpcf7_before_send_mail → ⑤ メール送信、の順に進みます。①〜③のどれかで引っかかると④には来ません。だから④で止めれば、「全部のチェックを通った入力だけ」が確認画面に進みます。
3-5. フォーム(CF7 の「フォーム」タブ)を1行ずつ
入力欄を bit-input、確認画面を bit-confirm で囲みます。全体です。
<div class="bit-input"> <div data-label="お名前"><label>お名前(必須)[text* your-name]</label></div> <div data-label="メールアドレス"><label>メールアドレス(必須)[email* your-email]</label></div> <div data-label="ご用件"><fieldset><legend>ご用件</legend>[radio kind use_label_element default:1 "お見積り" "ご質問" "その他"]</fieldset></div> <div data-label="興味のある分野"><fieldset><legend>興味のある分野</legend>[checkbox topics use_label_element "ホームページ制作" "システム開発" "保守"]</fieldset></div> <div data-label="お問い合わせ内容"><label>お問い合わせ内容(必須)[textarea* your-message]</label></div> [acceptance agree] 個人情報の取り扱いに同意する [/acceptance] <button type="submit" name="_bit_step" value="confirm" class="wpcf7-submit">入力内容を確認する</button> </div> <div class="bit-confirm"> <dl class="bit-confirm__table"></dl> <button type="button" class="bit-confirm__back">戻って修正する</button> [submit "送信する"] </div>
| 行 | コード | 何をしているか |
|---|---|---|
| 1 |
<div class="bit-input"> |
入力画面の部分の始まりです。確認画面ではこの部分が CSS で隠れます。 |
| 2 |
<div data-label="お名前"><label>お名前(必須)[text* your-name]</label></div> |
data-label="お名前" が確認表に出す項目名です。項目ごとに、この属性を付けた要素で囲みます。[text* your-name] は CF7 のタグで、* は必須の印です。 |
| 3 |
<div data-label="メールアドレス"><label>メールアドレス(必須)[email* your-email]</label></div> |
メールアドレスの欄です(email* はメールの形もチェックされます)。 |
| 4〜5 |
<div data-label="ご用件"><fieldset><legend>ご用件</legend>[radio kind use_label_element default:1 "お見積り" "ご質問" "その他"]</fieldset></div> <div data-label="興味のある分野"><fieldset><legend>興味のある分野</legend>[checkbox topics use_label_element "ホームページ制作" "システム開発" "保守"]</fieldset></div> |
ラジオボタンとチェックボックスです。チェックボックスは選んだものが「、」でつながって確認表に出ます。 |
| 6 |
<div data-label="お問い合わせ内容"><label>お問い合わせ内容(必須)[textarea* your-message]</label></div> |
複数行の入力欄です。確認表では改行もそのまま出ます。 |
| 7 |
[acceptance agree] 個人情報の取り扱いに同意する [/acceptance] |
同意のチェックです。CF7 は、ここにチェックが入るまで送信ボタン(wpcf7-submit の付いたボタン)を押せなくします。 |
| 8 |
<button type="submit" name="_bit_step" value="confirm" class="wpcf7-submit">入力内容を確認する</button> |
「入力内容を確認する」ボタンです。 ・ type="submit":押すとフォームを送信する(CF7 がふつうに入力チェックをする)・ name="_bit_step" value="confirm":押したときに「_bit_step=confirm」がいっしょに送られる。PHP の11行目はこれを見て「確認ボタンからだ」と判断します・ class="wpcf7-submit":付けると、7行目の「同意するまで押せない」がこのボタンにも効きますCF7 の [submit] タグでは name を付けられないので、HTML で直接書いています。入力中に Enter キーを押すと、最初にある送信ボタン(=このボタン)が押されたことになるので、うっかり送信されずに確認画面へ進みます。 |
| 9 |
</div> |
入力画面の部分の終わりです。 |
| 10 |
<div class="bit-confirm"> |
確認画面の部分の始まりです。ふだんは CSS で隠れています。 |
| 11 |
<dl class="bit-confirm__table"></dl> |
確認表の入れ物です。中身は JavaScript が作ります(dl は「項目名と内容」を並べるための HTML のタグです)。 |
| 12 |
<button type="button" class="bit-confirm__back">戻って修正する</button> [submit "送信する"] |
「戻って修正する」ボタンは type="button" なので、押しても送信しません(JavaScript が入力画面に戻します)。[submit "送信する"] は CF7 のふつうの送信ボタンです。_bit_step が付かないので、PHP の12行目で何もせずに終わり、いつもどおりメールが送られます。 |
| 13 |
</div> |
確認画面の部分の終わりです。 |
3-6. confirm.js を1行ずつ
/*
* bit CF7 確認画面(自作版)
* 「確認」で CF7 が status=bit_confirm を返したら、入力欄を隠して確認表を出す。
* 項目名はフォーム側の data-label を使う(<div data-label="お名前">[text* your-name]</div>)。
*/
document.addEventListener('wpcf7submit', function (e) {
var form = e.target;
var box = form.querySelector('.bit-confirm__table');
if (!box) return;
if (e.detail.status !== 'bit_confirm') {
// 確認画面の側の項目(クイズ・画像認証)の誤りなら、確認画面のまま直してもらう
var invalid = (e.detail.apiResponse && e.detail.apiResponse.invalid_fields) || [];
var onConfirmSide = invalid.length > 0 && invalid.every(function (f) {
return form.querySelector('.bit-confirm [name="' + f.field + '"]');
});
if (!onConfirmSide) {
form.classList.remove('is-confirm'); // 入力エラー・送信完了などは入力画面に戻す
}
return;
}
var data = new FormData(form);
box.textContent = '';
form.querySelectorAll('[data-label]').forEach(function (row) {
var names = new Set();
row.querySelectorAll('[name]').forEach(function (el) {
if (el.name.charAt(0) !== '_') names.add(el.name);
});
var values = [];
names.forEach(function (name) {
data.getAll(name).forEach(function (v) {
values.push(v instanceof File ? v.name : v);
});
});
var dt = document.createElement('dt');
var dd = document.createElement('dd');
dt.textContent = row.dataset.label;
dd.textContent = values.filter(function (v) { return v !== ''; }).join('、') || '(未入力)';
box.appendChild(dt);
box.appendChild(dd);
});
form.classList.add('is-confirm');
form.scrollIntoView({ behavior: 'smooth', block: 'start' });
// 押したボタンが隠れるので、キーボードの入力位置を確認表に移す
box.setAttribute('tabindex', '-1');
box.focus({ preventScroll: true });
});
document.addEventListener('click', function (e) {
var back = e.target.closest('.bit-confirm__back');
if (!back) return;
var form = back.closest('form');
form.classList.remove('is-confirm');
form.querySelectorAll('.wpcf7-response-output').forEach(function (el) { el.textContent = ''; });
// キーボードの入力位置を、最初の入力欄に戻す
var first = form.querySelector('.bit-input input:not([type=hidden]), .bit-input textarea, .bit-input select');
if (first) first.focus();
});
| 行 | コード | 何をしているか |
|---|---|---|
| 1〜5 |
/* * bit CF7 確認画面(自作版) * 「確認」で CF7 が status=bit_confirm を返したら、入力欄を隠して確認表を出す。 * 項目名はフォーム側の data-label を使う(<div data-label="お名前">[text* your-name]</div>)。 */ |
メモ(コメント)です。JavaScript では /* … */ や // の部分がメモです。 |
| 6 |
document.addEventListener('wpcf7submit', function (e) {
|
CF7 は、送信の結果がサーバから返ってくるたびに、wpcf7submit という合図(イベント)を出します。addEventListener() でその合図を待ち受け、合図が来たら中の処理を動かします。e に合図の中身が入ります。 |
| 7 |
var form = e.target; |
合図を出したフォームを form という名前で覚えます。 |
| 8〜9 |
var box = form.querySelector('.bit-confirm__table');
if (!box) return;
|
そのフォームの中から確認表の入れ物(フォームの11行目)を探します。見つからなければ何もしません(確認画面を使っていないふつうのフォームには影響しません)。 |
| 11 |
if (e.detail.status !== 'bit_confirm') {
|
e.detail.status は、サーバが付けた結果の名前です(PHP の15行目の bit_confirm など)。bit_confirm ではないとき(入力エラー・送信完了など)の処理がここから始まります。 |
| 12〜16 |
// 確認画面の側の項目(クイズ・画像認証)の誤りなら、確認画面のまま直してもらう
var invalid = (e.detail.apiResponse && e.detail.apiResponse.invalid_fields) || [];
var onConfirmSide = invalid.length > 0 && invalid.every(function (f) {
return form.querySelector('.bit-confirm [name="' + f.field + '"]');
});
|
エラーになった項目の一覧(invalid_fields)を調べ、エラーが確認画面の側の項目(クイズ・画像認証)だけかを確かめます。every() は「一覧の全部が条件に合うか」を調べる命令です。 |
| 17〜19 |
if (!onConfirmSide) {
form.classList.remove('is-confirm'); // 入力エラー・送信完了などは入力画面に戻す
}
|
そうでなければ、フォームから is-confirm という印を外して入力画面に戻します(入力のやり直しや、送信完了のとき)。確認画面の側のエラーなら、確認画面のまま答え直してもらいます。 |
| 20〜21 |
return;
}
|
ここで処理を終えます。 |
| 23 |
var data = new FormData(form); |
new FormData( form ) で、フォームに今入っている値を全部取り出します。 |
| 24 |
box.textContent = ''; |
確認表を空にします(前に確認したときの分を消すため)。 |
| 25 |
form.querySelectorAll('[data-label]').forEach(function (row) {
|
data-label の付いた要素(フォームの2〜6行目の囲み)を、上から1つずつ処理します。 |
| 26〜29 |
var names = new Set();
row.querySelectorAll('[name]').forEach(function (el) {
if (el.name.charAt(0) !== '_') names.add(el.name);
});
|
囲みの中にある入力欄の名前を集めます。Set は重複しない入れ物で、チェックボックスのように同じ名前の欄が複数あっても1回だけ数えます。名前が _ で始まるもの(CF7 が内部で使う欄)は除きます。 |
| 30〜35 |
var values = [];
names.forEach(function (name) {
data.getAll(name).forEach(function (v) {
values.push(v instanceof File ? v.name : v);
});
});
|
名前ごとに、入っている値を全部取り出します(getAll。チェックボックスは複数選べるため)。添付ファイルの欄なら、ファイル名を使います。 |
| 36〜37 |
var dt = document.createElement('dt');
var dd = document.createElement('dd');
|
確認表の1行分として、項目名用の dt と、内容用の dd を作ります。 |
| 38 |
dt.textContent = row.dataset.label; |
項目名(data-label に書いた文字)を入れます。 |
| 39 |
dd.textContent = values.filter(function (v) { return v !== ''; }).join('、') || '(未入力)';
|
内容を入れます。空の値は除き、複数あれば「、」でつなぎます。何も無ければ「(未入力)」と出します。 💡 textContent は文字として入れる書き方です。入力欄に <script> などを入れられても、文字のまま表示されて動きません(innerHTML を使うと動いてしまうので、使わないこと)。 |
| 40〜42 |
box.appendChild(dt);
box.appendChild(dd);
});
|
作った1行を確認表に足します。これを項目の数だけ繰り返します。 |
| 43 |
form.classList.add('is-confirm');
|
フォームに is-confirm という印を付けます。CSS(confirm.css の3〜4行目)がこの印を見て、入力画面を隠し、確認画面を出します。 |
| 44 |
form.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
フォームの先頭まで、なめらかにスクロールします。 |
| 45〜47 |
// 押したボタンが隠れるので、キーボードの入力位置を確認表に移す
box.setAttribute('tabindex', '-1');
box.focus({ preventScroll: true });
|
キーボードで操作している人のために、入力位置(フォーカス)を確認表に移します。押したボタンが隠れてしまうので、移さないと入力位置が行き場を失います。tabindex="-1" は、本来は入力位置を置けない dl に置けるようにする指定です。 |
| 48 |
}); |
合図を待ち受ける処理の終わりです。 |
| 50 |
document.addEventListener('click', function (e) {
|
ページの中のクリックを待ち受けます。 |
| 51〜52 |
var back = e.target.closest('.bit-confirm__back');
if (!back) return;
|
押された場所が「戻って修正する」ボタン(フォームの12行目)かを調べます。closest() は「自分か、自分を囲む要素の中から探す」命令です。違えば何もしません。 |
| 53〜54 |
var form = back.closest('form');
form.classList.remove('is-confirm');
|
そのボタンのフォームから is-confirm の印を外し、入力画面に戻します。入力欄は隠していただけなので、入力した値はそのまま残っています。 |
| 55 |
form.querySelectorAll('.wpcf7-response-output').forEach(function (el) { el.textContent = ''; });
|
フォームの下のメッセージを消します。 |
| 56〜59 |
// キーボードの入力位置を、最初の入力欄に戻す
var first = form.querySelector('.bit-input input:not([type=hidden]), .bit-input textarea, .bit-input select');
if (first) first.focus();
});
|
入力位置を、最初の入力欄(お名前)に戻します。 |
3-7. confirm.css を1行ずつ
/* 入力画面: 確認表と「送信する」を隠す/確認画面: 入力欄と「確認」を隠す */
.bit-confirm { display: none; }
form.is-confirm .bit-confirm { display: block; }
form.is-confirm .bit-input { display: none; }
.bit-confirm__table { display: grid; grid-template-columns: 10em 1fr; gap: .5em 1em; margin: 0 0 1.5em; }
.bit-confirm__table dt { font-weight: bold; }
.bit-confirm__table dd { margin: 0; white-space: pre-wrap; }
.bit-confirm__table:focus { outline: none; } /* 入力位置を移したときの枠を出さない(押す場所ではないため) */
| 行 | コード | 何をしているか |
|---|---|---|
| 1 |
/* 入力画面: 確認表と「送信する」を隠す/確認画面: 入力欄と「確認」を隠す */ |
メモです。 |
| 2 |
.bit-confirm { display: none; }
|
確認画面の部分(.bit-confirm)を、ふだんは隠します(display: none)。 |
| 3 |
form.is-confirm .bit-confirm { display: block; }
|
フォームに is-confirm の印が付いたら、確認画面の部分を出します。 |
| 4 |
form.is-confirm .bit-input { display: none; }
|
同じく印が付いたら、入力画面の部分(.bit-input)を隠します。 |
| 5 |
.bit-confirm__table { display: grid; grid-template-columns: 10em 1fr; gap: .5em 1em; margin: 0 0 1.5em; }
|
確認表を「項目名(10文字分)|内容」の2列に並べます(grid は格子状に並べる指定です)。 |
| 6 |
.bit-confirm__table dt { font-weight: bold; }
|
項目名を太字にします。 |
| 7 |
.bit-confirm__table dd { margin: 0; white-space: pre-wrap; }
|
内容の余白をなくし、入力された改行をそのまま表示します(pre-wrap)。 |
| 8 |
.bit-confirm__table:focus { outline: none; } /* 入力位置を移したときの枠を出さない(押す場所ではないため) */
|
JavaScript で入力位置を確認表に移したときに、ブラウザが描く枠を出さないようにします(確認表は押したり入力したりする場所ではないため)。 |
3-8. クイズ・画像認証を使う場合
CF7 の クイズ([quiz])や、Really Simple CAPTCHA の画像認証([captchac] [captchar])は、確認画面の側(フォームの11行目の確認表の下)に置きます。
<dl class="bit-confirm__table"></dl> <p>[quiz q1 "1+1は?|2"]</p> ← この1行を足す(画像認証なら [captchac cap1] [captchar cap1]) <button type="button" class="bit-confirm__back">戻って修正する</button> [submit "送信する"]
理由:CF7 は送信の結果を返すたびに、クイズの問題を出し直して答えの欄を空にします。入力画面の側に置くと、「確認」を押した時点で答えが消え、最後の送信で必ず「答えが正しくありません」になります(実測で、何度答えてもメールは0通でした)。確認画面の側に置き、PHP の24〜34行目で「確認の段階では答え合わせをしない」ようにしているので、最後の「送信する」のときにだけ答え合わせされます。答えを間違えたときは、確認画面のままエラーが出るので、その場で答え直せます(confirm.js の12〜19行目)。

3-9. 入れたら、この順に試してください
- 同意にチェックを入れる前は、「入力内容を確認する」が押せない
- 何も入れずに確認 → 必須のエラーが出て、確認画面に進まない
- 正しく入れて確認 → 確認画面に入力内容が並ぶ(この時点ではメールは届かない)
- 「戻って修正する」→ 入力した値が残っている → 直して再び確認すると、直した内容が出る
- 「送信する」→ メールが届く(自動返信を設定していれば、それも届く)
- CF7 やこのプラグインを更新したときも、1〜5 をもう一度試す
4. 作り方B:Confirm Plus Contact Form 7(入れるだけ)
プラグインを有効にするだけで、送信ボタンが「確認」に変わり、押すと確認表が出ます。項目名はフォームの <label> や <legend> から自動で拾います。今回の検証では、入力チェック・確認・修正・再確認・送信(メール2通)まで正しく動きました。

手軽ですが、検証で次の2点を確認しました。
Confirm Plus は、JavaScript が付ける「最終送信です」という印が無い送信を確認の段階とみなし、メールを送りません。JavaScript が動かない環境で送信ボタンを押すと、画面には「ありがとうございます。メッセージは送信されました。」と出るのに、メールは0通でした。このときは CF7 付属の保存機能(Flamingo)への記録も止める作りなので(コードで確認)、問い合わせはどこにも残りません。
(自作版は同じ条件で送信完了とは表示せず、フォームの上に「確認画面の表示には JavaScript が必要です」という案内を出します。)
Confirm Plus は、確認の段階でだけ迷惑メールの判定を行い、最終送信では判定を飛ばします(ソースコードのコメントには「最終送信での reCAPTCHA の誤判定を避けるため」とあります)。ところが「最終送信です」という印は送る側が自由に付けられるため、確認の段階を通らずに最終送信だけを送ると、判定を受けずにメールが送られます。WordPress の「禁止ワード」に登録した語を入れた送信で試すと、通常は迷惑メールとして止まるのに、この経路ではメールが2通送られました。reCAPTCHA・Akismet なども同じ判定の中で行われるため、コード上は同じく効かなくなります(こちらは実際のキーでは試していません)。
(自作版は確認・送信のどちらの経路でも判定が効き、同じ送信を止めました。)
5. Contact Form 7 add confirm は今の CF7 では動かない ★つまずきどころ
昔から確認画面づくりに使われてきた Contact Form 7 add confirm は、有効インストール数が5万件ありますが、最終更新は2018年2月です。CF7 の版を変えて試した結果は次のとおりです。
| CF7 の版 | 結果 |
|---|---|
| 5.3.2 | ✅ 確認 → 戻る/送信 → メール2通まで動く |
| 5.4(2021年2月)/5.5.5/5.9.8/6.2.4.1 | ❌ 確認ボタンを何度押しても確認画面に進まない。メッセージもエラーも出ず、メールも送られない |
原因は、プラグインの JavaScript が CF7 から届く合図(wpcf7submit)の中の detail.id で、切り替えるフォームを探していることです。CF7 5.4 で JavaScript が作り直され、この detail.id が合図に入らなくなりました(フォームを見分ける値としては、以前からある detail.unitTag が残っています)。サーバ側は「確認OK」を返しているのに、画面側がどのフォームを切り替えればよいか分からず止まります。CF7 の配布ファイルで、5.3.2 にはあって 5.4 には無いことを確かめました。
この壊れ方はエラーが出ないため、管理画面からも分かりません。add confirm を使っているサイトで CF7 を 5.4 以降に更新すると、お問い合わせを受け取れないままになります。CF7 の更新後は、確認画面 → 送信 → メールの受信まで実際に試してください。
6. 実測結果の一覧
| 確かめたこと | A. 自作 | B. Confirm Plus | C. add confirm |
|---|---|---|---|
| 同意する前はボタンが押せない | ✅ | ✅ | ✅ |
| 空のまま確認 → 必須エラー・メール0通 | ✅ | ✅ | ✅ |
| メール形式の誤り → エラー・メール0通 | ✅ | ✅ | ✅ |
| 正しく入力 → 確認画面・メール0通 | ✅ | ✅ | ❌ 進まない |
| <b> や <script> が文字のまま表示される | ✅ | ✅ | − |
| 戻る → 値が残る → 修正 → 再確認に反映 | ✅ | ✅ | − |
| 送信 → 管理者宛て+自動返信の2通 | ✅ | ✅ | − |
| 入力中に Enter → 確認画面へ(送信されない) | ✅ | (未確認) | − |
| JavaScript が動かない環境 | 案内を表示・メール0通(偽の完了は出ない) | ⚠ 「送信されました」と出てメール0通 | − |
| 禁止ワード入りの送信(ふつうの経路) | ✅ 止まる | ✅ 止まる | − |
| 禁止ワード入りの送信(最終送信だけを直接送る) | ✅ 止まる | ⚠ メール2通 | − |
| クイズ([quiz]):間違えると確認画面のままエラー、正しく答えると送信 | ✅ | (未確認) | − |
| 画像認証(Really Simple CAPTCHA 2.5):同上 | ✅ | (未確認) | − |
| 問い合わせ保存(CFDB7 1.4.2):「確認」だけでは記録されず、送信で1件 | ✅ | (未確認) | − |
| 添付ファイル・複数選択のリスト・同じページに2つのフォーム・ボタンの連打 | ✅ ファイル名が確認表に出て添付も届く/連打してもメールは2通だけ | (未確認) | − |
| キーボードだけで操作(確認 → 戻る) | ✅ 入力位置が確認表 → 最初の入力欄へ移る | (未確認) | − |
7. 作るときの注意点
どの作り方でも、確認画面を通らずに送信だけを直接送ることはできます。ですから、入力チェックと迷惑メール対策は最終送信のときに必ず効いている必要があります。自作版は CF7 本体のチェックをそのまま使い、クイズ・画像認証も最終送信で答え合わせするので、この点を気にせずに済みます(確認画面で入力値を書き換えて送られても、最終送信のチェックは通ります)。
自作版は、JavaScript が動かないブラウザでは確認画面を出せません。そのため、フォームの上に「確認画面の表示には JavaScript が必要です。JavaScript が使えない場合は、お電話またはメールでお問い合わせください。」という案内を自動で出します(PHP の38〜43行目。文言はサイトに合わせて変えてください)。このとき送信完了と誤って表示することはありません。
確認表は
data-label の付いた要素だけを並べます。フォームに項目を足したときは、その項目にも data-label を付けてください。付け忘れると確認表に出ません(送信されるメールには入ります)。
PHP の15行目と confirm.js の11行目は同じ名前にそろえる必要があります。ハイフンなどを使うと CF7 が名前を受け付けず、確認画面が出なくなります。
CF7 の reCAPTCHA(v3)は、送信のたびに新しい判定を取り直す作りなので、自作版の「確認 → 送信」の2回の送信でも問題ない作りです(CF7 のコードで確認。実際のキーでは試していません)。Turnstile も今回は試していないので、使う場合は 3-9 の手順で、確認 → 送信まで試してから公開してください。
確認画面は CF7 の合図(
wpcf7submit)や送信の仕組みに乗って作るため、CF7 の大きな更新で動かなくなることがあります(5章の add confirm がその例です)。更新後は、確認画面 → 送信 → メールの受信まで試してください。
8. まとめ(ご質問への回答)
wpcf7_before_send_mail で「確認ボタンから来た送信」だけメールを送らずに止め、その間に JavaScript で確認表を出します。PHP・JavaScript・CSS の3ファイルで作れ、入力チェックと迷惑メール対策は CF7 の設定どおりに効きます。クイズ・画像認証・問い合わせ保存プラグインとも一緒に使えることを確かめました(3章でコードを1行ずつ説明しています)。手早く済ませたい場合は Confirm Plus Contact Form 7 が入れるだけで動きますが、JavaScript が動かないと偽の送信完了になること、最終送信で迷惑メールの判定をしないことを理解して使ってください(4章)。
古くからある Contact Form 7 add confirm は、CF7 5.4 以降では確認画面に進めません。使っているサイトがあれば、CF7 を更新する前に作り方を見直してください(5章)。
参考(一次ソース)
- Contact Form 7(WordPress.org): https://wordpress.org/plugins/contact-form-7/
- Confirm Plus Contact Form 7(WordPress.org): https://wordpress.org/plugins/confirm-plus-contact-form-7/
- Contact Form 7 add confirm(WordPress.org): https://wordpress.org/plugins/contact-form-7-add-confirm/
- Multi-Step Forms for Contact Form 7(WordPress.org): https://wordpress.org/plugins/contact-form-7-multi-step-module/
- MW WP Form(WordPress.org): https://wordpress.org/plugins/mw-wp-form/
- Snow Monkey Forms(WordPress.org): https://wordpress.org/plugins/snow-monkey-forms/
- add_action()(WordPress 公式): https://developer.wordpress.org/reference/functions/add_action/
※本記事の動作は、WordPress 7.1.4(日本語版)/PHP 8.3/MySQL 8.0 の検証環境で、Contact Form 7 6.2.4.1(add confirm は 5.3.2・5.4・5.5.5・5.9.8 でも確認)・Confirm Plus Contact Form 7 2.0.3.1・Contact Form 7 add confirm 5.1・Really Simple CAPTCHA 2.5・Contact Form 7 Database Addon(CFDB7)1.4.2 を使い、2026年11月4日に確認しました。プラグインの版・更新日・有効インストール数は同日の WordPress.org の情報です。
作成: Seeds Brains(ジェイノーム業務支援AI)