Require admin approval for new WordPress user registrations: a plugin you can build today

· 4 min read

WordPress gives you two options for user registration: open or closed. Enable it under Settings, General, and anyone with an email address gets an account and can log in the moment they submit the form. Disable it, and every account has to be created by hand in the admin. There is nothing in between.

Most sites that need registration need something in between. A community site for local event organisers, a school intranet, a B2B supplier portal: all of these want a signup form that works, but they want a human to review each application before the account becomes active. The native tools offer no way to hold a registration in a pending state.

A small plugin closes that gap. When someone registers, their account is created but assigned a role with no capabilities. You get an email with their details and one-click approve and deny links. If they try to log in before you act, they see a clear pending message rather than being let through or getting a confusing error. Approval promotes them to subscriber and sends a welcome email. The whole flow runs without any third-party dependency.

What one build gives you

  • Custom pending role assigned to all new registrations, with no capabilities until approved
  • Admin email notification with one-click approve and deny links for each new signup
  • Login block for pending accounts with a status-specific message instead of a generic error
  • Admin notice on the Users screen with pending count and a direct filter link

What it does not do

  • Custom registration form fields beyond username, email, and password
  • Frontend login page: still uses wp-login.php
  • Invitation codes or token-based registration gating
  • Automatic account deletion on denial
  • WooCommerce checkout registration: customers would also go through the approval step unless customised

The prompt

Loads into the composer so you can edit it first. Nothing is built, and nothing is charged, until you send it.

Build a WordPress plugin called Pending Registration Approval. Plugin Name: Pending Registration Approval. Description: Holds new user registrations in a pending state until an admin approves or denies them. Single PHP file. No external dependencies. On activation (register_activation_hook), call add_role('pending_user', 'Pending User', []) if the role does not already exist. On deactivation (register_deactivation_hook), do two things in order: query all users with the pending_user role using WP_User_Query and set each to subscriber using wp_update_user, so no accounts are stranded without a role; then call remove_role('pending_user'). Hook into user_register at priority 10. In the callback: (1) Call wp_update_user(['ID' => $user_id, 'role' => 'pending_user']) to override the default role assignment. (2) Add a one-time filter on wp_new_user_notification_email that returns an array with 'to' set to '' to suppress the default user email. Add a one-time filter on wp_new_user_notification_email_admin that returns an array with 'to' set to '' to suppress the default admin email. These filters must remove themselves after firing. (3) Retrieve the WP_User object with get_userdata($user_id). Send the registering user a custom email via wp_mail: to their email address, subject '[' . get_bloginfo('name') . '] Your registration is under review', body explaining their account is awaiting admin review and they will receive an email once it has been approved. (4) Build an approve URL using wp_nonce_url(admin_url('admin-post.php?action=preg_approve&user_id=' . $user_id), 'preg_approve_' . $user_id). Build a deny URL using wp_nonce_url(admin_url('admin-post.php?action=preg_deny&user_id=' . $user_id), 'preg_deny_' . $user_id). Send the admin an email via wp_mail: to get_option('admin_email'), subject '[' . get_bloginfo('name') . '] New registration pending approval: ' . $user->user_login, body including the username, email address, and registration time, followed by the approve URL and deny URL on separate labelled lines. Hook into authenticate at priority 30. If the passed value is a WP_User instance and in_array('pending_user', (array) $user->roles) is true, return a new WP_Error with code 'account_pending' and message 'Your account is pending admin approval. You will receive an email once it has been reviewed.' Register admin-post handlers using add_action('admin_post_preg_approve', ...) and add_action('admin_post_preg_deny', ...). Both handlers must: read user_id from $_GET using absint; verify the nonce using check_admin_referer; check current_user_can('manage_options') and call wp_die with a permission error if not. The preg_approve handler: call wp_update_user(['ID' => $user_id, 'role' => 'subscriber']); retrieve the user with get_userdata; send them an email via wp_mail: subject '[' . get_bloginfo('name') . '] Your account has been approved', body confirming their account is active with a login link built from wp_login_url(); redirect to admin_url('users.php?preg_action=approved') using wp_safe_redirect then exit. The preg_deny handler: call wp_update_user(['ID' => $user_id, 'role' => '']); retrieve the user with get_userdata; send them an email: subject '[' . get_bloginfo('name') . '] Your registration was not approved', body stating their registration was not approved and they may contact the site administrator with questions; redirect to admin_url('users.php?preg_action=denied') using wp_safe_redirect then exit. Add an admin_notices hook. Only when the global $pagenow equals 'users.php' and is_admin() is true. Query users with the pending_user role using new WP_User_Query(['role' => 'pending_user', 'fields' => 'ID']). If get_total() is greater than zero, output a div with class notice notice-warning and a paragraph reading: '{n} user registration(s) are pending your approval.' where {n} is the total count, with a link to admin_url('users.php?role=pending_user') with link text 'Review pending accounts'. If $_GET contains preg_action set to 'approved', also output a dismissible div with class notice notice-success containing 'Account approved and the user notified.' If preg_action is 'denied', output 'Registration denied and the user notified.'

Build this pluginAbout 55 credits · the free plan includes enough for one

Why the default registration settings do not help

When open registration is on, WordPress assigns every new account the role set under Default User Role, typically subscriber, and the person can log in immediately. The only way to slow that down with native tools is to disable registration entirely and create accounts by hand from Users, Add New.

Plugins like BuddyPress and MemberPress both include approval flows, but they bring a lot more along with them: profile pages, activity streams, and membership tiers in one case; content restriction and payment gates in the other. If the requirement is an approval gate and nothing else, either of those installs machinery you will never use.

The problem is common enough to have a clean, self-contained solution. A plugin that registers a pending role, intercepts registrations, blocks login, and exposes approve and deny actions covers the full requirement in a single PHP file.

What the plugin does

When a new user submits the registration form, the plugin assigns them a custom role called pending_user. This role has no capabilities, so the account exists in the database but cannot access anything on the site.

The default WordPress registration email is replaced. The new user gets a message explaining their account is under review and they will hear back once it has been approved. They do not receive the standard password setup email.

The admin gets a notification email for each new signup, naming the user and their email address and including a direct Approve link and a direct Deny link. Each link is signed with a nonce. Clicking Approve from that email promotes the account to subscriber and sends the user a welcome email with a link to the login page. Clicking Deny sets the account to no role and sends the user a short message.

Until approved, any login attempt shows the message: Your account is pending admin approval. You will receive an email once it has been reviewed. This is specific to the pending state and distinct from the standard wrong password error.

The Users screen in the admin shows a notice whenever any pending accounts exist, with a count and a link to filter the list by the pending_user role so you can review them in bulk.

  • Assigns all new registrations a pending role with no capabilities
  • Replaces the default WordPress registration email with a pending review message
  • Sends the admin a notification email with one-click approve and deny links for each new signup
  • Blocks login for pending accounts with a status-specific message
  • Admin notice on the Users screen listing how many accounts are awaiting review

What the plugin does not do

The registration form is unchanged. Username, email address, and password: those are what WordPress provides. If you need additional fields on the signup form, such as a company name or a short reason for joining, that is a separate piece the plugin does not touch.

This does not create a frontend login page. WordPress's default wp-login.php handles login. If you want a styled frontend login form that matches your theme, ask Steem to build one separately.

The plugin does not support invitation codes or token-based gates. Anyone who can reach the registration URL can submit the form. The gatekeeping is manual review by an admin, not a cryptographic check.

Denial does not delete the account by default. The account stays in the database with no role assigned. Deleting it permanently is a separate step from the Users screen.

Extending the plugin

The most common addition is extra fields on the registration form. Once you have the plugin, ask Steem to add a company name field and a short reason-for-joining textarea, and to include those answers in the admin notification email so you have context when you approve or deny.

If you expect a high signup volume and one email per registration would become noise, ask for a digest mode: a single daily email listing all pending accounts instead of individual notifications.

If your site uses WooCommerce and you want shop customers to register without approval while gating site account registrations separately, ask Steem to add a setting that detects the WooCommerce checkout context and skips the pending step for that path.

Questions

Does this work with custom registration forms, not just the default wp-login.php one?

Yes. The plugin hooks into user_register, which fires whenever any code path creates a WordPress user account. Custom theme registration forms, shortcode-based forms, and plugin-generated registration flows all trigger the same hook.

Will existing users be affected when the plugin is activated?

No. Only accounts created after activation are assigned the pending role. Existing subscribers, editors, and administrators are not changed.

What happens if a user is approved but never received the notification email?

Once their account is approved and set to subscriber, they can use the standard forgot password flow at wp-login.php to set or retrieve their password. The approval step handles the role; the password mechanics are handled by WordPress.

Does this work alongside WooCommerce checkout registration?

The hook fires for all registrations including those created during WooCommerce checkout. Customers completing a purchase would also go into the pending state, which is likely not what you want. If your site uses WooCommerce, ask Steem to add logic that bypasses the pending step when registration is triggered from the checkout.

Can I see all pending registrations without waiting for an email?

Yes. The plugin adds an admin notice on the Users screen with a direct link to filter the user list by the pending_user role. You can also navigate there manually by adding ?role=pending_user to the users.php URL.

The prompt

Loads into the composer so you can edit it first. Nothing is built, and nothing is charged, until you send it.

Build a WordPress plugin called Pending Registration Approval. Plugin Name: Pending Registration Approval. Description: Holds new user registrations in a pending state until an admin approves or denies them. Single PHP file. No external dependencies. On activation (register_activation_hook), call add_role('pending_user', 'Pending User', []) if the role does not already exist. On deactivation (register_deactivation_hook), do two things in order: query all users with the pending_user role using WP_User_Query and set each to subscriber using wp_update_user, so no accounts are stranded without a role; then call remove_role('pending_user'). Hook into user_register at priority 10. In the callback: (1) Call wp_update_user(['ID' => $user_id, 'role' => 'pending_user']) to override the default role assignment. (2) Add a one-time filter on wp_new_user_notification_email that returns an array with 'to' set to '' to suppress the default user email. Add a one-time filter on wp_new_user_notification_email_admin that returns an array with 'to' set to '' to suppress the default admin email. These filters must remove themselves after firing. (3) Retrieve the WP_User object with get_userdata($user_id). Send the registering user a custom email via wp_mail: to their email address, subject '[' . get_bloginfo('name') . '] Your registration is under review', body explaining their account is awaiting admin review and they will receive an email once it has been approved. (4) Build an approve URL using wp_nonce_url(admin_url('admin-post.php?action=preg_approve&user_id=' . $user_id), 'preg_approve_' . $user_id). Build a deny URL using wp_nonce_url(admin_url('admin-post.php?action=preg_deny&user_id=' . $user_id), 'preg_deny_' . $user_id). Send the admin an email via wp_mail: to get_option('admin_email'), subject '[' . get_bloginfo('name') . '] New registration pending approval: ' . $user->user_login, body including the username, email address, and registration time, followed by the approve URL and deny URL on separate labelled lines. Hook into authenticate at priority 30. If the passed value is a WP_User instance and in_array('pending_user', (array) $user->roles) is true, return a new WP_Error with code 'account_pending' and message 'Your account is pending admin approval. You will receive an email once it has been reviewed.' Register admin-post handlers using add_action('admin_post_preg_approve', ...) and add_action('admin_post_preg_deny', ...). Both handlers must: read user_id from $_GET using absint; verify the nonce using check_admin_referer; check current_user_can('manage_options') and call wp_die with a permission error if not. The preg_approve handler: call wp_update_user(['ID' => $user_id, 'role' => 'subscriber']); retrieve the user with get_userdata; send them an email via wp_mail: subject '[' . get_bloginfo('name') . '] Your account has been approved', body confirming their account is active with a login link built from wp_login_url(); redirect to admin_url('users.php?preg_action=approved') using wp_safe_redirect then exit. The preg_deny handler: call wp_update_user(['ID' => $user_id, 'role' => '']); retrieve the user with get_userdata; send them an email: subject '[' . get_bloginfo('name') . '] Your registration was not approved', body stating their registration was not approved and they may contact the site administrator with questions; redirect to admin_url('users.php?preg_action=denied') using wp_safe_redirect then exit. Add an admin_notices hook. Only when the global $pagenow equals 'users.php' and is_admin() is true. Query users with the pending_user role using new WP_User_Query(['role' => 'pending_user', 'fields' => 'ID']). If get_total() is greater than zero, output a div with class notice notice-warning and a paragraph reading: '{n} user registration(s) are pending your approval.' where {n} is the total count, with a link to admin_url('users.php?role=pending_user') with link text 'Review pending accounts'. If $_GET contains preg_action set to 'approved', also output a dismissible div with class notice notice-success containing 'Account approved and the user notified.' If preg_action is 'denied', output 'Registration denied and the user notified.'

Build this pluginAbout 55 credits · the free plan includes enough for one

Read next

Other plugins you can build this way

Each loads into the composer, ready to edit.