2026年9月6日
2026年9月6日
WordPressで招待制のユーザー登録を実装する方法
WordPressで招待した人だけが登録できるようにするには、少人数なら「一般の登録を閉じて管理者がユーザーを追加する」方法で足ります。招待した本人に自分で登録してもらいたい場合は、招待リンクに一度だけ使えるトークンを付け、登録時にそのトークンとメールアドレスを照合する仕組みを作ります。この記事では、コア機能だけで済ませる方法と、registration_errors などのフックを使って招待リンク方式を実装する方法を、つまずきやすい点と確認手順を含めて解説します。
方法の選び方
招待制にする方法は、招待する人数と、誰がアカウント情報を入力するかで選びます。
- 管理者がユーザーを追加する(コア機能のみ): 数人〜数十人程度で、ユーザー名やメールアドレスを管理者が把握している場合に向いています。追加の開発は不要です
- 招待リンク方式(フックで実装): 招待された人が自分でユーザー名を決めて登録する形にしたい場合や、招待の発行を繰り返す場合に向いています
- 会員登録プラグインの招待機能: 有料会員や承認フローなど、招待以外の会員機能も必要な場合は、招待コードの機能を持つ会員プラグインを選ぶほうが保守の負担を抑えられます
どの方法でも共通する前提は、「誰でも登録できる入口を残さない」ことです。招待の仕組みを作っても、別の登録フォームが開いたままでは意味がありません。
コア機能だけで招待制にする
一般のユーザー登録を閉じる
管理画面の「設定」→「一般」にある「メンバーシップ」の「だれでもユーザー登録ができるようにする」のチェックを外します。これで wp-login.php?action=register からの登録はできなくなります。あわせて、同じ画面の「新規ユーザーのデフォルト権限グループ」が「購読者」など最小限の権限になっているかも確認してください。ここが高い権限になっていると、登録を開いたときに大きな事故につながります。
管理者がユーザーを追加して案内メールを送る
「ユーザー」→「新規ユーザーを追加」(バージョンによっては「新規追加」)で、ユーザー名とメールアドレスを入力し、新規ユーザーにアカウントに関するメールを送信する項目にチェックを入れて追加します。相手にはパスワードを設定するためのリンクが届くため、管理者がパスワードを決めて伝える必要はありません。
この方法の注意点は、パスワード設定用のリンクには有効期限があることです。WordPressの既定では1日で(2026年10月現在)、期限が切れた場合は相手がログイン画面の「パスワードをお忘れですか?」から再発行できます。期限を延ばしたい場合は password_reset_expiration フィルターで変更できますが、パスワード再設定のリンク全体に影響するため、むやみに長くしないでください。
招待リンク方式を実装する
招待された人が自分で登録する形にするには、一般のユーザー登録を開いたうえで、招待リンクを持たない登録をすべて拒否します。仕組みは次のとおりです。
- 管理者が招待先のメールアドレスを指定して、ランダムなトークン付きの登録URLを発行する
- 招待された人がそのURLから登録画面を開くと、トークンがフォームに引き継がれる
- 登録時に、トークンが有効か、期限内か、メールアドレスが招待先と一致するかを確認する
- 登録が完了したらトークンを削除し、同じリンクを再利用できないようにする
設定の「だれでもユーザー登録ができるようにする」にはチェックを入れ、デフォルト権限グループは「購読者」にしておきます。次のコードは wp-content/mu-plugins/ に置くことを想定しています。追加する前にサイトのバックアップを取り、ステージング環境で動作を確かめてください。
<?php
/**
* Plugin Name: Invitation Only Registration (site-specific)
* Description: 招待リンクを持つ人だけが wp-login.php から登録できるようにする。
*/
/**
* 招待を発行して登録URLを返す。招待先にはメールで送る。
*/
function mysite_create_invitation( $email, $days = 7 ) {
$email = sanitize_email( $email );
if ( ! is_email( $email ) ) {
return new WP_Error( 'invalid_email', 'メールアドレスが正しくありません。' );
}
if ( email_exists( $email ) ) {
return new WP_Error( 'email_exists', 'このメールアドレスは登録済みです。' );
}
$invitations = get_option( 'mysite_invitations', array() );
// 期限切れの招待を掃除する
foreach ( $invitations as $key => $invitation ) {
if ( $invitation['expires'] < time() ) {
unset( $invitations[ $key ] );
}
}
$token = wp_generate_password( 32, false );
$invitations[ hash( 'sha256', $token ) ] = array(
'email' => strtolower( $email ),
'expires' => time() + $days * DAY_IN_SECONDS,
);
update_option( 'mysite_invitations', $invitations, false );
$url = add_query_arg( 'invite', $token, wp_registration_url() );
wp_mail(
$email,
sprintf( '[%s] ご招待のお知らせ', wp_specialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES ) ),
sprintf( "次のURLから、このメールアドレスで登録してください(%d日間有効)。\n\n%s", $days, $url )
);
return $url;
}
// 登録フォームにトークンを引き継ぐ
add_action( 'register_form', function () {
$invite = isset( $_REQUEST['invite'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['invite'] ) ) : '';
printf( '<input type="hidden" name="invite" value="%s">', esc_attr( $invite ) );
} );
// 登録時に招待を確認する
add_filter( 'registration_errors', function ( $errors, $sanitized_user_login, $user_email ) {
$invite = isset( $_POST['invite'] ) ? sanitize_text_field( wp_unslash( $_POST['invite'] ) ) : '';
$invitations = get_option( 'mysite_invitations', array() );
$key = hash( 'sha256', $invite );
if ( '' === $invite || ! isset( $invitations[ $key ] ) ) {
$errors->add( 'invite_invalid', '<strong>エラー:</strong> 招待リンクからのみ登録できます。' );
} elseif ( $invitations[ $key ]['expires'] < time() ) {
$errors->add( 'invite_expired', '<strong>エラー:</strong> 招待リンクの有効期限が切れています。' );
} elseif ( strtolower( $user_email ) !== $invitations[ $key ]['email'] ) {
$errors->add( 'invite_email', '<strong>エラー:</strong> 招待を受け取ったメールアドレスで登録してください。' );
}
return $errors;
}, 10, 3 );
// 登録が完了したら招待を使用済みにする
add_action( 'register_new_user', function ( $user_id ) {
$invite = isset( $_POST['invite'] ) ? sanitize_text_field( wp_unslash( $_POST['invite'] ) ) : '';
if ( '' === $invite ) {
return;
}
$invitations = get_option( 'mysite_invitations', array() );
unset( $invitations[ hash( 'sha256', $invite ) ] );
update_option( 'mysite_invitations', $invitations, false );
} );
コードのポイント
- トークンはハッシュで保存する: データベースにはトークンそのものではなくSHA-256のハッシュを保存しています。データベースの内容が漏れても、そこから有効な招待リンクを作られにくくするためです
- メールアドレスと照合する: 招待リンクが転送されても、招待先以外のメールアドレスでは登録できません。登録後にWordPressが送るパスワード設定メールも招待先に届くため、本人確認を兼ねられます
- 招待の保存先はオプション: 招待の件数が数百件程度までならオプション1つで十分です。大量に発行する場合は専用のテーブルやカスタム投稿タイプに分けることを検討します
招待を発行する
管理画面を作らずに済ませるなら、WP-CLIの wp eval で関数を呼び出すのが手軽です。
wp eval 'var_dump( mysite_create_invitation( "user@example.com" ) );'
登録URLの文字列が表示され、招待先にメールが送られれば成功です。エラーの場合は WP_Error の内容が表示されます。管理画面から発行したい場合は、管理者だけが開ける設定ページを作り、フォームにはnonceを付けて current_user_can( 'create_users' ) で権限を確認したうえで、この関数を呼び出します。
動作を確認する
確認は、少なくとも次の4つの場合で行います。
- 招待リンクなしで
wp-login.php?action=registerを開いて登録すると、「招待リンクからのみ登録できます」と表示される - 招待リンクから、招待先とは別のメールアドレスで登録すると拒否される
- 招待リンクから招待先のメールアドレスで登録すると成功し、パスワード設定メールが届く
- 同じ招待リンクで再度登録しようとすると拒否される
招待メールやパスワード設定メールが届かない場合は、サーバーからの送信が迷惑メールに分類されている可能性があります。SMTP経由で送信する設定にし、送信ドメインの認証を整えてください。
つまずきやすい点
- ほかの登録フォームが開いている: この実装が守るのは
wp-login.phpの登録だけです。WooCommerceの「マイアカウント」ページの登録や、会員プラグインが用意する独自の登録フォームは別の処理を通るため、それぞれの設定で登録を止めるか、同様の確認を追加します - マルチサイトでは仕組みが違う: マルチサイトの登録は
wp-signup.phpで処理され、ここで紹介したフックとは別の流れになります。この記事のコードは単一サイト向けです - ページキャッシュ: 登録画面がキャッシュされると、別の人のトークンが入ったフォームが表示されるおそれがあります。
wp-login.phpはキャッシュの対象から外しておきます - 招待リンクの取り扱い: 招待リンクはパスワードに近い扱いの情報です。チャットや掲示板など、複数人が見られる場所には貼らないよう、招待する側にも案内しておきます
まとめ
- 少人数なら、一般の登録を閉じて「ユーザー」→「新規ユーザーを追加」で招待するのが最も手間が少ない
- 本人に登録してもらう招待リンク方式は、
register_form・registration_errors・register_new_userの3つのフックで実装できる - トークンはハッシュで保存し、有効期限とメールアドレスの照合、使用後の削除まで行う
- 招待以外の登録フォーム、マルチサイト、ページキャッシュなど、招待を迂回できる経路が残っていないかを確認する
- 動作確認は「招待なし」「別アドレス」「正しい登録」「再利用」の4パターンで行う