閉じる

技術ブログ

【WordPress】Contact Form 7 に確認画面を付ける ── Multi-Step Forms を使わない方法を試してみた

2026.11.04

WordPress 実践シリーズ #10
Contact Form 7 に確認画面を付ける
── Multi-Step Forms を使わない方法を試してみた
「入力 → 確認 → 送信」の流れを、確認画面用のプラグインを使わない自作と、確認画面用のプラグインで実際に作り、
最新の Contact Form 7(6.2.4.1)で入力チェックから送信まで動かして比べました

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月)以降は確認ボタンを押しても先に進みません。エラーも出ないので、気づかないまま問い合わせを受け取れなくなります。
自作版の確認画面。入力した項目名と内容が表になって並び、「戻って修正する」「送信する」ボタンがある
今回作った自作版の確認画面。入力欄が隠れて、項目名と入力内容が並びます(見た目は CSS で自由に変えられます)

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行)
  1. wp-content/plugins/ の中に bit-cf7-confirm というフォルダを作り、下の3つのファイルを置きます。
  2. 管理画面の「プラグイン」に「bit CF7 確認画面(自作の見本)」が出るので、有効化します。
  3. 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_filter
1組ずつ、左を $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 がチェックする順番
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行目)。

クイズを確認画面の側に置いた例。確認表の下に「1+1は?」の入力欄がある
クイズを確認画面の側に置いた例。答えてから「送信する」を押します

3-9. 入れたら、この順に試してください

  1. 同意にチェックを入れる前は、「入力内容を確認する」が押せない
  2. 何も入れずに確認 → 必須のエラーが出て、確認画面に進まない
  3. 正しく入れて確認 → 確認画面に入力内容が並ぶ(この時点ではメールは届かない)
  4. 「戻って修正する」→ 入力した値が残っている → 直して再び確認すると、直した内容が出る
  5. 「送信する」→ メールが届く(自動返信を設定していれば、それも届く)
  6. CF7 やこのプラグインを更新したときも、1〜5 をもう一度試す

4. 作り方B:Confirm Plus Contact Form 7(入れるだけ)

プラグインを有効にするだけで、送信ボタンが「確認」に変わり、押すと確認表が出ます。項目名はフォームの <label> や <legend> から自動で拾います。今回の検証では、入力チェック・確認・修正・再確認・送信(メール2通)まで正しく動きました。

Confirm Plus Contact Form 7 の確認画面。項目名と入力内容の表の下に「修正」「この内容で送信」ボタンがある
Confirm Plus の確認画面(テーマ Twenty Twenty-Five)。表は自動で作られます。ボタンはフォームの外に置かれるため、テーマによっては位置や見た目の調整が要ります

手軽ですが、検証で次の2点を確認しました。

⚠ 注意1:JavaScript が動かないと「送信されました」と出るのにメールが届かない
Confirm Plus は、JavaScript が付ける「最終送信です」という印が無い送信を確認の段階とみなし、メールを送りません。JavaScript が動かない環境で送信ボタンを押すと、画面には「ありがとうございます。メッセージは送信されました。」と出るのに、メールは0通でした。このときは CF7 付属の保存機能(Flamingo)への記録も止める作りなので(コードで確認)、問い合わせはどこにも残りません。
(自作版は同じ条件で送信完了とは表示せず、フォームの上に「確認画面の表示には JavaScript が必要です」という案内を出します。)
⚠ 注意2:最終送信では迷惑メールの判定をしない
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 には無いことを確かめました。

⚠ CF7 を更新したら、確認画面のあるフォームは必ず送信まで試す
この壊れ方はエラーが出ないため、管理画面からも分かりません。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 が必要です。JavaScript が使えない場合は、お電話またはメールでお問い合わせください。」という案内を自動で出します(PHP の38〜43行目。文言はサイトに合わせて変えてください)。このとき送信完了と誤って表示することはありません。
③ 項目を足したら data-label も足す
確認表は data-label の付いた要素だけを並べます。フォームに項目を足したときは、その項目にも data-label を付けてください。付け忘れると確認表に出ません(送信されるメールには入ります)。
④ 結果の名前(bit_confirm)を変えるときは、英小文字・数字・アンダーバーだけで
PHP の15行目と confirm.js の11行目は同じ名前にそろえる必要があります。ハイフンなどを使うと CF7 が名前を受け付けず、確認画面が出なくなります。
⑤ reCAPTCHA・Turnstile について
CF7 の reCAPTCHA(v3)は、送信のたびに新しい判定を取り直す作りなので、自作版の「確認 → 送信」の2回の送信でも問題ない作りです(CF7 のコードで確認。実際のキーでは試していません)。Turnstile も今回は試していないので、使う場合は 3-9 の手順で、確認 → 送信まで試してから公開してください。
⑥ CF7 を更新したら送信まで試す
確認画面は CF7 の合図(wpcf7submit)や送信の仕組みに乗って作るため、CF7 の大きな更新で動かなくなることがあります(5章の add confirm がその例です)。更新後は、確認画面 → 送信 → メールの受信まで試してください。

8. まとめ(ご質問への回答)

Q. Contact Form 7 で確認画面を作りたい。Multi-Step Forms を使う以外に方法はありますか?
あります。おすすめは自作です。CF7 の 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章)。

参考(一次ソース)

※本記事の動作は、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)