
How to create a custom user registration form in WordPress?
You can create a custom user registration form in WordPress in two main ways: use a plugin such as WPForms, Gravity Forms, User Registration & Membership, or Profile Builder to build the form visually, or write your own form in a small custom plugin that validates the input and creates the account with wp_insert_user(). Either way, you place the form on a regular page so visitors can sign up without ever seeing the default wp-login.php screen.
The default WordPress registration page works, but it's plain, unbranded, and only asks for a username and email. A custom form lets you match your site's design, collect extra information, control which role new users receive, and redirect them wherever you like. This article covers both the plugin approach and a complete, secure code-based approach, plus spam protection and best practices.
Why Create a Custom Registration Form?
A custom registration form gives you much more control than the default one:
- Branding: The form lives on a normal page with your theme's header, footer, and styles.
- Custom fields: Collect first name, last name, company, phone number, or anything else you need.
- Better user experience: Let users choose their own password, show helpful error messages, and redirect them to a welcome page after signing up.
- Role control: Assign a specific role based on the form, such as Subscriber, Customer, or Contributor.
- Approval workflows: Some plugins let you manually approve new accounts before they become active.
- Integration: Connect registrations to email marketing tools, CRMs, or membership systems.
Before You Start: Enable User Registration
Whichever method you choose, make sure your site allows registrations and uses a safe default role:
- Go to Settings > General.
- Tick Anyone can register next to Membership. (Some plugins handle registration independently, but enabling this keeps behavior consistent.)
- Set New User Default Role to Subscriber.
- Click Save Changes.
Never set the default role to anything powerful, such as Editor or Administrator, on a site with public registration.
Method 1: Create a Registration Form With a Plugin
A plugin is the fastest option and requires no code. Several well-known plugins can build registration forms.
WPForms: A drag-and-drop form builder. User registration forms are available through its User Registration addon, which requires a paid license. You can map form fields to user fields, choose roles, and enable email activation or admin approval.
Gravity Forms: A premium form plugin with a User Registration Add-On for creating accounts, mapping fields to user meta, and updating profiles.
User Registration & Membership: A dedicated free plugin (from WPEverest) with a drag-and-drop builder for registration forms, login forms, and user profile pages, with more features in paid add-ons.
Profile Builder: Provides front-end registration, login, and profile edit forms, with custom fields and redirects available in paid versions.
Ultimate Member: A popular free plugin for community and membership sites, offering registration forms, profiles, and member directories.
General Steps With a Plugin
The exact interface differs, but the process usually looks like this:
-
Install and activate the plugin from Plugins > Add New Plugin, or upload the premium plugin you've purchased.
-
Create a new form: Choose a registration template if the plugin offers one.
-
Add fields: Include the essentials, such as username (or use email as username), email, and password, plus any custom fields you need.
-
Map fields to user data: Link form fields to WordPress user fields like first name, last name, and website, and to custom user meta for extra fields.
-
Choose the user role: Select which role new registrants receive.
-
Configure activation: Decide whether users are active immediately, need to confirm their email, or require admin approval.
-
Set up notifications: Customize the email users receive and the notification sent to administrators.
-
Add spam protection: Enable CAPTCHA, Cloudflare Turnstile, or honeypot options.
-
Publish the form: Add it to a page with the plugin's block or shortcode, for example, a page called "Sign Up."
-
Test it: Register as a test user in a private browser window and confirm the account is created with the right role and data.
Redirecting the Default Registration Page
Once your custom form is live, you may want to send people away from the default wp-login.php?action=register URL. Many registration plugins include a setting for this. You can also add a simple redirect in code (shown later in this article).
Method 2: Build a Custom Registration Form With Code
If you prefer full control and no plugin dependencies, you can build your own registration form. The approach below uses:
- A shortcode to display the form on any page.
- A nonce to protect against cross-site request forgery.
- The
admin_post_nopriv_hook to process submissions from logged-out users. - WordPress functions like
sanitize_user(),is_email(),username_exists(),email_exists(), andwp_insert_user()for validation and account creation. - A honeypot field to catch simple bots.
Put all of this in a custom plugin file, for example wp-content/plugins/sajjad-registration/sajjad-registration.php. Test on a staging site before using it in production.
Step 1: Create the Plugin and Register the Shortcode
<?php
/**
* Plugin Name: Sajjad Custom Registration
* Description: A custom front-end user registration form.
* Version: 1.0.0
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Render the registration form.
*/
function sajjad_registration_form_shortcode() {
if ( is_user_logged_in() ) {
return '<p>' . esc_html__( 'You are already registered and logged in.', 'sajjad' ) . '</p>';
}
if ( ! get_option( 'users_can_register' ) ) {
return '<p>' . esc_html__( 'Registration is currently closed.', 'sajjad' ) . '</p>';
}
$output = '';
// Show errors passed back from the handler.
if ( isset( $_GET['sajjad_reg_error'] ) ) {
$error_key = sanitize_key( wp_unslash( $_GET['sajjad_reg_error'] ) );
$messages = sajjad_registration_error_messages();
if ( isset( $messages[ $error_key ] ) ) {
$output .= '<div class="sajjad-reg-error" role="alert">' . esc_html( $messages[ $error_key ] ) . '</div>';
}
}
ob_start();
?>
<form class="sajjad-reg-form" method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>">
<p>
<label for="sajjad_first_name"><?php esc_html_e( 'First name', 'sajjad' ); ?></label>
<input type="text" name="sajjad_first_name" id="sajjad_first_name" autocomplete="given-name" required>
</p>
<p>
<label for="sajjad_last_name"><?php esc_html_e( 'Last name', 'sajjad' ); ?></label>
<input type="text" name="sajjad_last_name" id="sajjad_last_name" autocomplete="family-name">
</p>
<p>
<label for="sajjad_username"><?php esc_html_e( 'Username', 'sajjad' ); ?></label>
<input type="text" name="sajjad_username" id="sajjad_username" autocomplete="username" required>
</p>
<p>
<label for="sajjad_email"><?php esc_html_e( 'Email', 'sajjad' ); ?></label>
<input type="email" name="sajjad_email" id="sajjad_email" autocomplete="email" required>
</p>
<p>
<label for="sajjad_password"><?php esc_html_e( 'Password (at least 12 characters)', 'sajjad' ); ?></label>
<input type="password" name="sajjad_password" id="sajjad_password" autocomplete="new-password" minlength="12" required>
</p>
<p>
<label for="sajjad_company"><?php esc_html_e( 'Company (optional)', 'sajjad' ); ?></label>
<input type="text" name="sajjad_company" id="sajjad_company" autocomplete="organization">
</p>
<!-- Honeypot: hidden from humans, often filled in by bots. -->
<p class="sajjad-hp" aria-hidden="true">
<label for="sajjad_website_url">Leave this field empty</label>
<input type="text" name="sajjad_website_url" id="sajjad_website_url" tabindex="-1" autocomplete="off">
</p>
<input type="hidden" name="action" value="sajjad_register_user">
<?php wp_nonce_field( 'sajjad_register_user', 'sajjad_register_nonce' ); ?>
<p>
<button type="submit"><?php esc_html_e( 'Create account', 'sajjad' ); ?></button>
</p>
</form>
<?php
return $output . ob_get_clean();
}
add_shortcode( 'sajjad_registration_form', 'sajjad_registration_form_shortcode' );
Step 2: Define Error Messages
Keeping error messages in one function makes the form easier to maintain and translate:
/**
* Registration error messages keyed by code.
*/
function sajjad_registration_error_messages() {
return array(
'nonce' => __( 'Your session expired. Please try again.', 'sajjad' ),
'closed' => __( 'Registration is currently closed.', 'sajjad' ),
'empty' => __( 'Please fill in all required fields.', 'sajjad' ),
'invalid_user' => __( 'That username contains invalid characters.', 'sajjad' ),
'user_exists' => __( 'That username is already taken.', 'sajjad' ),
'invalid_email' => __( 'Please enter a valid email address.', 'sajjad' ),
'email_exists' => __( 'An account with that email already exists.', 'sajjad' ),
'weak_password' => __( 'Please choose a password with at least 12 characters.', 'sajjad' ),
'failed' => __( 'Something went wrong. Please try again later.', 'sajjad' ),
);
}
Step 3: Process the Form Submission
This handler runs when a logged-out visitor submits the form. It verifies the nonce, checks the honeypot, validates and sanitizes every field, creates the user, saves custom meta, logs them in, and redirects.
/**
* Handle registration form submissions.
*/
function sajjad_handle_registration() {
$form_url = wp_get_referer() ? wp_get_referer() : home_url( '/' );
$redirect_with_error = function ( $code ) use ( $form_url ) {
wp_safe_redirect( add_query_arg( 'sajjad_reg_error', $code, $form_url ) );
exit;
};
if ( ! isset( $_POST['sajjad_register_nonce'] ) ||
! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['sajjad_register_nonce'] ) ), 'sajjad_register_user' ) ) {
$redirect_with_error( 'nonce' );
}
if ( ! get_option( 'users_can_register' ) ) {
$redirect_with_error( 'closed' );
}
// Honeypot check: silently stop bots.
if ( ! empty( $_POST['sajjad_website_url'] ) ) {
wp_safe_redirect( home_url( '/' ) );
exit;
}
$raw_username = isset( $_POST['sajjad_username'] ) ? wp_unslash( $_POST['sajjad_username'] ) : '';
$username = sanitize_user( $raw_username, true );
$email = isset( $_POST['sajjad_email'] ) ? sanitize_email( wp_unslash( $_POST['sajjad_email'] ) ) : '';
$password = isset( $_POST['sajjad_password'] ) ? (string) wp_unslash( $_POST['sajjad_password'] ) : '';
$first_name = isset( $_POST['sajjad_first_name'] ) ? sanitize_text_field( wp_unslash( $_POST['sajjad_first_name'] ) ) : '';
$last_name = isset( $_POST['sajjad_last_name'] ) ? sanitize_text_field( wp_unslash( $_POST['sajjad_last_name'] ) ) : '';
$company = isset( $_POST['sajjad_company'] ) ? sanitize_text_field( wp_unslash( $_POST['sajjad_company'] ) ) : '';
if ( '' === $username || '' === $email || '' === $password || '' === $first_name ) {
$redirect_with_error( 'empty' );
}
if ( $username !== $raw_username || ! validate_username( $username ) ) {
$redirect_with_error( 'invalid_user' );
}
if ( username_exists( $username ) ) {
$redirect_with_error( 'user_exists' );
}
if ( ! is_email( $email ) ) {
$redirect_with_error( 'invalid_email' );
}
if ( email_exists( $email ) ) {
$redirect_with_error( 'email_exists' );
}
if ( strlen( $password ) < 12 ) {
$redirect_with_error( 'weak_password' );
}
$user_id = wp_insert_user(
array(
'user_login' => $username,
'user_email' => $email,
'user_pass' => $password,
'first_name' => $first_name,
'last_name' => $last_name,
'role' => get_option( 'default_role', 'subscriber' ),
)
);
if ( is_wp_error( $user_id ) ) {
$redirect_with_error( 'failed' );
}
if ( '' !== $company ) {
update_user_meta( $user_id, 'sajjad_company', $company );
}
// Notify the site admin about the new registration.
wp_new_user_notification( $user_id, null, 'admin' );
// Log the new user in.
wp_set_current_user( $user_id );
wp_set_auth_cookie( $user_id );
wp_safe_redirect( home_url( '/welcome/' ) );
exit;
}
add_action( 'admin_post_nopriv_sajjad_register_user', 'sajjad_handle_registration' );
A few notes on this code:
admin_post_nopriv_only fires for logged-out users, which is exactly who should be registering.sanitize_user()with strict mode strips characters WordPress doesn't allow in usernames. Comparing it with the raw input lets you show a clear error rather than silently changing the username.wp_insert_user()hashes the password for you. Never store raw passwords.- The role comes from your settings, never from the form. Letting users pick their role from a form field would be a serious security hole.
/welcome/is an example. Create a welcome page or change the URL to wherever you want new users to land.
Step 4: Hide the Honeypot and Style the Form
Add CSS to your child theme's stylesheet or the Additional CSS panel:
.sajjad-hp {
position: absolute;
left: -9999px;
width: 1px;
height: 1px;
overflow: hidden;
}
.sajjad-reg-form {
max-width: 480px;
}
.sajjad-reg-form label {
display: block;
margin-bottom: 0.25rem;
font-weight: 600;
}
.sajjad-reg-form input {
width: 100%;
padding: 0.6rem 0.75rem;
border: 1px solid #ccc;
border-radius: 4px;
}
.sajjad-reg-error {
margin-bottom: 1rem;
padding: 0.75rem 1rem;
border-left: 4px solid #d63638;
background: #fcf0f1;
}
Step 5: Add the Form to a Page
- Go to Pages > Add New Page and create a page such as "Sign Up."
- Add a Shortcode block.
- Enter
[sajjad_registration_form]. - Publish the page and test it in a private browser window.
Step 6: Show Custom Fields in the Admin Profile
To let administrators see the extra "Company" field on each user's profile, add:
/**
* Display the company field on user profile screens.
*/
function sajjad_show_company_field( $user ) {
?>
<h2><?php esc_html_e( 'Additional Information', 'sajjad' ); ?></h2>
<table class="form-table" role="presentation">
<tr>
<th><label for="sajjad_company"><?php esc_html_e( 'Company', 'sajjad' ); ?></label></th>
<td>
<input type="text" name="sajjad_company" id="sajjad_company" class="regular-text"
value="<?php echo esc_attr( get_user_meta( $user->ID, 'sajjad_company', true ) ); ?>">
</td>
</tr>
</table>
<?php
}
add_action( 'show_user_profile', 'sajjad_show_company_field' );
add_action( 'edit_user_profile', 'sajjad_show_company_field' );
/**
* Save the company field from profile screens.
*/
function sajjad_save_company_field( $user_id ) {
if ( ! current_user_can( 'edit_user', $user_id ) ) {
return;
}
// WordPress verifies the profile nonce before these hooks run.
if ( isset( $_POST['sajjad_company'] ) ) {
update_user_meta( $user_id, 'sajjad_company', sanitize_text_field( wp_unslash( $_POST['sajjad_company'] ) ) );
}
}
add_action( 'personal_options_update', 'sajjad_save_company_field' );
add_action( 'edit_user_profile_update', 'sajjad_save_company_field' );
Step 7: Redirect the Default Registration Page (Optional)
To send visitors from the default registration screen to your custom page:
/**
* Redirect the default registration page to the custom sign-up page.
*/
function sajjad_redirect_default_registration() {
wp_safe_redirect( home_url( '/sign-up/' ) );
exit;
}
add_action( 'login_form_register', 'sajjad_redirect_default_registration' );
The login_form_register action fires when someone visits wp-login.php?action=register.
Protecting Your Registration Form From Spam
Registration forms are prime targets for bots. Layer a few defenses:
- Honeypot fields: Simple and invisible to real users, as shown above.
- CAPTCHA or Turnstile: Services like Google reCAPTCHA, hCaptcha, and Cloudflare Turnstile add a stronger check. Many form plugins include these, and plugins like Simple Cloudflare Turnstile can protect custom forms.
- Email verification: Require new users to click a link before their account becomes active. Most registration plugins support this.
- Admin approval: For private communities, manually approve each new account.
- Rate limiting: Security plugins and firewalls can block IPs that submit many registrations in a short time.
Privacy and Legal Considerations
When you collect personal data, you're responsible for handling it properly:
- Only collect what you need: Every extra field is more data to protect.
- Link to your privacy policy: Add a short note and link near the submit button. WordPress helps you create one under Settings > Privacy.
- Add a consent checkbox if needed: Depending on where your users are and what you do with their data, you may need explicit consent, especially for marketing emails.
- Support data export and erasure: WordPress includes tools under Tools > Export Personal Data and Tools > Erase Personal Data.
Testing Your Registration Form
Before announcing your new form:
- Register a test account in a private browser window.
- Try invalid input: Duplicate usernames, invalid emails, short passwords, and empty fields should all show clear errors.
- Check the user in Users > All Users: Confirm the role and custom fields are correct.
- Check emails: Make sure notification emails arrive. If not, install an SMTP plugin such as WP Mail SMTP or FluentSMTP.
- Test on mobile: Confirm the form is easy to fill in on small screens.
FAQ: Custom User Registration Forms in WordPress
Yes. You can build a form in a small custom plugin using a shortcode, a nonce, the admin_post_nopriv_ hook, and wp_insert_user() to create the account, as shown in this article.
Popular choices include WPForms with its User Registration addon, Gravity Forms with its User Registration Add-On, User Registration & Membership, Profile Builder, and Ultimate Member. The best one depends on your budget and required features.
Plugins let you add fields visually and map them to user meta. In code, add the input to your form, sanitize it in your handler, and save it with update_user_meta().
Combine a honeypot field, a CAPTCHA or Cloudflare Turnstile, email verification, and rate limiting. For private communities, manual approval of new accounts is also effective.
They shouldn't in most cases. Letting users pick roles like Editor or Administrator is a major security risk. Assign roles on the server side, or offer a limited set of safe roles using a trusted plugin.
Most registration plugins include a redirect setting. In custom code, call wp_safe_redirect() with your chosen URL at the end of the registration handler.
Conclusion
A custom user registration form makes signing up on your WordPress site feel like part of your brand rather than a detour to a generic login screen. Plugins like WPForms, Gravity Forms, User Registration & Membership, and Profile Builder let you build one visually in minutes, complete with custom fields, approval workflows, and spam protection.
If you prefer full control, a small custom plugin with a shortcode, a nonce, careful validation, and wp_insert_user() gives you a lightweight, secure form tailored exactly to your needs. Whichever route you take, assign roles on the server side, protect the form against bots, collect only the data you need, and test thoroughly before you launch.


