2026年8月25日
2026年8月25日
WordPressで有効期限付きのユーザーアカウントを作る方法
WordPress 本体には、ユーザーアカウントに有効期限を付ける機能はありません。実現するには、会員制プラグインの「期間付きメンバーシップ」を使うか、ユーザーメタに期限日を保存してログイン時にチェックする小さなプラグインを自作します。この記事では、外部の協力者や体験版ユーザーに「この日まで使えるアカウント」を発行したい人向けに、両方の考え方と、そのまま使えるコードを紹介します。
方法を選ぶ:プラグインか自作か
会員制プラグインが向いているケースは、期限付きアカウントが「有料会員」や「申し込み制」と結び付いている場合です。Paid Memberships Pro、MemberPress、Restrict Content Pro などの会員制プラグインは、会員レベルごとに期間を設定でき、期限が来ると会員資格が切れる仕組みを備えています。決済や更新通知まで一緒に扱えるのが利点ですが、機能が大きく、単に「この人のアカウントを月末で止めたい」だけなら過剰です。
自作が向いているケースは、管理者が手動でアカウントを発行し、期限日だけを管理したい場合です。外注先ライター、短期の制作パートナー、検証用の一時アカウントなどがこれにあたります。必要なのは次の4つだけで、合わせても数十行で済みます。
- ユーザー編集画面に期限日の入力欄を出し、ユーザーメタに保存する
- ログイン時に期限を過ぎていれば拒否する
- すでにログインしているセッションも切る
- 一覧画面で期限が分かるようにする
WordPress.org には「アカウントの期限切れ」を扱う単機能プラグインもありますが、更新が止まっているものも見られます。導入する場合は、最終更新日と対応バージョンを確認してから選んでください。
自作プラグインのコード
wp-content/plugins/account-expiry/account-expiry.php として保存し、プラグイン一覧から有効化します。本番に入れる前に、ステージング環境かバックアップを取った状態で試してください。
<?php
/**
* Plugin Name: Account Expiry
* Description: ユーザーごとに有効期限を設定し、期限後のログインを止める。
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
const AE_META_KEY = 'ae_expires_at'; // UNIX タイムスタンプを保存する
function ae_get_expiry( $user_id ) {
return (int) get_user_meta( $user_id, AE_META_KEY, true );
}
function ae_is_expired( $user_id ) {
// 管理者は対象外にして、締め出し事故を防ぐ
if ( user_can( $user_id, 'manage_options' ) ) {
return false;
}
$expires = ae_get_expiry( $user_id );
return $expires > 0 && $expires <= time();
}
/* 1. ユーザー編集画面に入力欄を出す */
function ae_profile_field( $user ) {
if ( ! current_user_can( 'edit_users' ) ) {
return;
}
$expires = ae_get_expiry( $user->ID );
$value = $expires ? wp_date( 'Y-m-d', $expires ) : '';
wp_nonce_field( 'ae_save_expiry_' . $user->ID, 'ae_expiry_nonce' );
?>
<h2>アカウントの有効期限</h2>
<table class="form-table" role="presentation">
<tr>
<th><label for="ae_expires">有効期限</label></th>
<td>
<input type="date" name="ae_expires" id="ae_expires" value="<?php echo esc_attr( $value ); ?>">
<p class="description">この日の 23:59:59(サイトのタイムゾーン)を過ぎるとログインできなくなります。空欄なら無期限です。</p>
</td>
</tr>
</table>
<?php
}
add_action( 'show_user_profile', 'ae_profile_field' );
add_action( 'edit_user_profile', 'ae_profile_field' );
function ae_save_expiry( $user_id ) {
if ( ! current_user_can( 'edit_users' ) || ! current_user_can( 'edit_user', $user_id ) ) {
return;
}
$nonce = isset( $_POST['ae_expiry_nonce'] ) ? sanitize_text_field( wp_unslash( $_POST['ae_expiry_nonce'] ) ) : '';
if ( ! wp_verify_nonce( $nonce, 'ae_save_expiry_' . $user_id ) ) {
return;
}
$raw = isset( $_POST['ae_expires'] ) ? sanitize_text_field( wp_unslash( $_POST['ae_expires'] ) ) : '';
if ( '' === $raw ) {
delete_user_meta( $user_id, AE_META_KEY );
return;
}
$date = DateTimeImmutable::createFromFormat( '!Y-m-d', $raw, wp_timezone() );
if ( ! $date ) {
return;
}
update_user_meta( $user_id, AE_META_KEY, $date->setTime( 23, 59, 59 )->getTimestamp() );
}
add_action( 'personal_options_update', 'ae_save_expiry' );
add_action( 'edit_user_profile_update', 'ae_save_expiry' );
/* 2. 期限切れのユーザーのログインを拒否する */
add_filter( 'authenticate', function ( $user ) {
if ( $user instanceof WP_User && ae_is_expired( $user->ID ) ) {
return new WP_Error( 'ae_account_expired', 'このアカウントは有効期限が切れています。管理者にお問い合わせください。' );
}
return $user;
}, 99 );
// アプリケーションパスワード(REST API の Basic 認証)も止める
add_filter( 'wp_is_application_passwords_available_for_user', function ( $available, $user ) {
return ae_is_expired( $user->ID ) ? false : $available;
}, 10, 2 );
/* 3. ログイン済みのセッションを切る */
add_action( 'init', function () {
$user_id = get_current_user_id();
if ( $user_id && ae_is_expired( $user_id ) ) {
WP_Session_Tokens::get_instance( $user_id )->destroy_all();
wp_logout();
wp_safe_redirect( wp_login_url() );
exit;
}
} );
// 1日1回、期限切れユーザーのセッションをまとめて破棄する
add_action( 'ae_daily_expire', function () {
$user_ids = get_users( array(
'fields' => 'ID',
'meta_query' => array(
array(
'key' => AE_META_KEY,
'value' => time(),
'compare' => '<=',
'type' => 'NUMERIC',
),
),
) );
foreach ( $user_ids as $user_id ) {
if ( ae_is_expired( (int) $user_id ) ) {
WP_Session_Tokens::get_instance( (int) $user_id )->destroy_all();
}
}
} );
add_action( 'init', function () {
if ( ! wp_next_scheduled( 'ae_daily_expire' ) ) {
wp_schedule_event( time(), 'daily', 'ae_daily_expire' );
}
} );
register_deactivation_hook( __FILE__, function () {
wp_clear_scheduled_hook( 'ae_daily_expire' );
} );
/* 4. ユーザー一覧に有効期限の列を足す */
add_filter( 'manage_users_columns', function ( $columns ) {
$columns['ae_expires'] = '有効期限';
return $columns;
} );
add_filter( 'manage_users_custom_column', function ( $output, $column_name, $user_id ) {
if ( 'ae_expires' !== $column_name ) {
return $output;
}
$expires = ae_get_expiry( $user_id );
if ( ! $expires ) {
return '—';
}
$label = wp_date( 'Y-m-d', $expires );
return esc_html( ae_is_expired( $user_id ) ? $label . '(期限切れ)' : $label );
}, 10, 3 );
コードのポイントと、つまずきやすい所
期限は「タイムスタンプ」で保存する
入力欄は日付ですが、保存するのは UNIX タイムスタンプです。wp_timezone() でサイトのタイムゾーン(設定 → 一般)を使い、その日の終わりに変換しています。日付文字列のまま保存すると、比較のたびにタイムゾーンの解釈がずれる原因になります。サーバーの時刻が UTC、サイトが日本時間という構成はよくあるため、「9時間早く切れた」という問い合わせを防ぐ意味があります。
ログイン拒否は authenticate の後ろの方で行う
authenticate フィルターでは、優先度 20 前後で WordPress 本体がユーザー名とパスワードを検証します。優先度 99 にしておけば、パスワードが正しいと分かった後の WP_User に対してだけ期限をチェックできます。パスワードが間違っている人に「期限切れです」と表示して、アカウントの存在を教えてしまうことも避けられます。
ログイン済みの人は、何もしないと使い続けられる
ログインを拒否しても、すでに持っている認証クッキーは有効なままです。「ログイン状態を保存する」を選んだユーザーなら最長で2週間程度そのまま使えてしまいます。WP_Session_Tokens::get_instance()->destroy_all() でサーバー側のセッションを破棄すると、クッキーが残っていても次のリクエストからログアウト扱いになります。この記事のコードでは、アクセス時のチェックと1日1回の一括破棄の両方で対応しています。
WP-Cron はアクセスがないと動かない
wp_schedule_event() で登録した処理は、サイトへのアクセスをきっかけに実行されます。アクセスの少ないサイトでは実行が遅れるため、サーバーの cron から WP-Cron を定期実行する設定にしておくと安定します。なお、ログイン拒否とアクセス時のログアウトは WP-Cron に依存していないので、Cron が遅れても期限切れのユーザーが使い続けることはありません。
期限後のアカウントをどう扱うか
このコードは、期限が来てもアカウントを削除しません。投稿者として記事を書いてもらっていた場合、ユーザーを削除すると記事の扱い(削除するか別ユーザーに引き継ぐか)を決める必要が出るためです。権限そのものも外したい場合は、日次処理の中で ( new WP_User( $user_id ) )->set_role( '' ); を実行すると、アカウントを残したままロールを空にできます。削除する運用にするなら、その前にデータベースのバックアップを取ってください。
動作を確かめる
期限付きのテストユーザーを作り、期限を過去の日付にして確認します。WP-CLI ならユーザー作成と期限の設定をまとめて行えます。
wp user create temp-writer temp-writer@example.com --role=author
wp user meta update temp-writer ae_expires_at "$(date -d '2026-10-01 23:59:59' +%s)"
date -d はサーバーのタイムゾーンで解釈されるため、日本時間で指定したい場合は TZ=Asia/Tokyo date -d ... のように書きます(GNU date の書式。2026年10月現在の一般的な Linux サーバーで使えます)。
確認するのは次の3点です。
- ログイン画面で正しいパスワードを入れると、期限切れのメッセージが出る
- 期限を過去に変える前からログインしていたブラウザで、次の画面遷移時にログイン画面へ戻される
- ユーザー一覧の「有効期限」列に「(期限切れ)」と表示される
最後に、管理者アカウントには期限を設定しても影響がないことも確かめておくと安心です。
まとめ
- WordPress 本体に有効期限の機能はなく、会員制プラグインか自作で実現する
- 決済や更新と結び付くなら会員制プラグイン、手動発行の一時アカウントなら自作が手軽
- 期限はサイトのタイムゾーンで日の終わりに変換し、タイムスタンプで保存する
- ログイン拒否だけでなく、既存セッションの破棄とアプリケーションパスワードの停止まで行う
- 期限後は削除より「ログイン不可+ロールを外す」が安全。削除するならバックアップを先に取る