How to add custom fields to the WordPress registration form

If you allow registrations on your WordPress site, chances are you need to gather some more information about your users. If you absolutely need this information, the best way to get it is to make it required during registration, otherwise there are slim chances that your users will actually visit their profile page and provide that optional information.

Sure, there are countless free and premium plugins that already do that, but you may want to do it manually, either because the plugins don’t do exactly what you want, or because you want to keep your website as lightweight as possible. Yes, there are tutorials also that tell you how to do it, but my research tells me that they are incomplete. What they all seem to be missing is new user registrations from the back-end (you know, when you -the administrator- add a user manually via the dashboard). Heck, the related WordPress codex page doesn’t even mention back-end registrations.

For this tutorial, we’ll add a required field that accepts the user’s year of birth and will be visible in the profile page. We won’t pay too much attention on validation, as it varies per use-case, but we will sanitize appropriately. Let’s see what it takes to add a new, required field in the default WordPress registration forms, from the start (what the user uses), to the middle (what the admin uses), to the end (what both see on the profile page).

The boilerplate

If your read our blog regularly, you’ve probably already guessed that we’ll create a new plugin to hold our code. If you’re not sure why, make sure you read our article regarding plugins vs child themes first. Create a new file in /wp-content/plugins/ named registration-fields.php and paste the following content:

<?php
/*
Plugin Name: Custom Registration Fields
Plugin URI:
Description:
Version: 0.1
Author: CSSIgniter
Author URI:
License: GPLv2 or later
License URI: http://www.gnu.org/licenses/gpl-2.0.html
*/
registration-fields.php

Feel free to fill in the blanks or change any of the information. Save and activate the plugin.

Front-end registration

First of all, we’ll deal with the visitors’ facing registration form, as it’s usually the most used one. It requires that you’ve enabled user registration by going to Settings → General and checking the Anyone can register checkbox. If you’re working on a live website where really anyone can register, I strongly recommend adding a method to prevent spam registrations. Personally, I use the Invisible reCaptcha for WordPress plugin, and I’m very happy with it.

Screenshot of the default WordPress registration form.

The default WordPress registration form.

Displaying the field

Add the following in your plugin file:

/**
 * Front end registration
 */

add_action( 'register_form', 'crf_registration_form' );
function crf_registration_form() {

	$year = ! empty( $_POST['year_of_birth'] ) ? intval( $_POST['year_of_birth'] ) : '';

	?>
	<p>
		<label for="year_of_birth"><?php esc_html_e( 'Year of birth', 'crf' ) ?><br/>
			<input type="number"
			       min="1900"
			       max="2017"
			       step="1"
			       id="year_of_birth"
			       name="year_of_birth"
			       value="<?php echo esc_attr( $year ); ?>"
			       class="input"
			/>
		</label>
	</p>
	<?php
}

The action ‘register_form‘ fires right after the email field, so that’s where our new field will appear. Unfortunately there isn’t any other place we can hook. Line 8 checks the existence and sanitizes the value, in order to re-populate it in line 19. This is useful when the form re-appears, for example when a required value is missing or is wrong.

Right now we can visit the registration page and we’ll see that the field appears. You can find the link to your registration page, either by assigning the Meta widget (it includes a link to the registration form), or visiting directly http://your-website/wp-login.php?action=register

Screenshot of the default WordPress registration form with a Year of birth field

The default WordPress registration form with a Year of birth field.

Validating the field

If you now enter a username and email, and leave the year of birth is empty, no error will be thrown. Since we said the field will be required, let’s go ahead and fix that. Add the following:

add_filter( 'registration_errors', 'crf_registration_errors', 10, 3 );
function crf_registration_errors( $errors, $sanitized_user_login, $user_email ) {

	if ( empty( $_POST['year_of_birth'] ) ) {
		$errors->add( 'year_of_birth_error', __( '<strong>ERROR</strong>: Please enter your year of birth.', 'crf' ) );
	}

	if ( ! empty( $_POST['year_of_birth'] ) && intval( $_POST['year_of_birth'] ) < 1900 ) {
		$errors->add( 'year_of_birth_error', __( '<strong>ERROR</strong>: You must be born after 1900.', 'crf' ) );
	}

	return $errors;
}

The ‘registration_errors‘ filter fires before the user is created, so we have a chance of adding our own error messages if something is missing, and avoid creating a user record with incomplete information. All we need is to $errors->add() our errors depending on our requirements. In this case, two different messages are used, depending if the year is missing or is less than 1900. This is were you’d validate a bit more, if for example you wanted to make sure a user is older than 18 years old, based on the year (or more other fields) he/she provided. Finally, we need to return $errors. While we don’t use $sanitized_user_login and $user_email, you could use them to throw more errors, if for example the user’s username contains your brand name.

Now, go ahead and fill in some of the registration form fields. Leave the year of birth empty, or enter a value less than 1900 and press Register. You should see a box listing all errors, including “Please enter your year of birth.” or “You must be born after 1900.“, the ones we added in the code inside crf_registration_errors().

Sanitizing and saving the field

If you do enter a valid year of birth at this point, a user will be registered but the year will not be saved. Add the following code to the plugin file:

add_action( 'user_register', 'crf_user_register' );
function crf_user_register( $user_id ) {
	if ( ! empty( $_POST['year_of_birth'] ) ) {
		update_user_meta( $user_id, 'year_of_birth', intval( $_POST['year_of_birth'] ) );
	}
}

Action ‘user_register‘ fires after a user is inserted into the database. Only now can we store their year of birth, as we needed the user ID in order to work with user metadata. Make sure a value exists, and sanitize the value by passing it through intval() as it definitely needs to be an integer.

At this point, you may go ahead and fill in all the registration fields for a hypothetical new user. Once you press the Register button and no errors are thrown, their year of birth will have been stored in your database. How can you confirm that the value is actually stored? You can’t right now, not unless you want to dive into the actual database tables (it’s the wp_usermeta table, in case you’re interested). If you’re impatient to see that it works, jump to the Profile display section below, and then come back here to continue.

Back-end registration

It’s time to tackle the back-end registration, that is, the form that an administrator (or other role that can) registers users manually. This might be the only way to add new users if you don’t enable user registrations from Settings → General. You might consider this a bit redundant, but we shouldn’t leave any cases uncovered.

Screenshot of the default back-end WordPress registration form.

The default back-end WordPress registration form.

Displaying the field

The back-end registration form has a different format (and more fields) than the front-end form, so it requires different markup, not to mention that there are a whole different lot of actions and filters. To add display the year of birth field, add the following code to the plugin file:

/**
 * Back end registration
 */

add_action( 'user_new_form', 'crf_admin_registration_form' );
function crf_admin_registration_form( $operation ) {
	if ( 'add-new-user' !== $operation ) {
		// $operation may also be 'add-existing-user'
		return;
	}

	$year = ! empty( $_POST['year_of_birth'] ) ? intval( $_POST['year_of_birth'] ) : '';

	?>
	<h3><?php esc_html_e( 'Personal Information', 'crf' ); ?></h3>

	<table class="form-table">
		<tr>
			<th><label for="year_of_birth"><?php esc_html_e( 'Year of birth', 'crf' ); ?></label> <span class="description"><?php esc_html_e( '(required)', 'crf' ); ?></span></th>
			<td>
				<input type="number"
			       min="1900"
			       max="2017"
			       step="1"
			       id="year_of_birth"
			       name="year_of_birth"
			       value="<?php echo esc_attr( $year ); ?>"
			       class="regular-text"
				/>
			</td>
		</tr>
	</table>
	<?php
}

Just like the ‘register_form‘ action, the ‘user_new_form‘ action fires after all fields are displayed, so again, our field will be placed last before the Add New User button. Now, the ‘user_new_form‘ action is used twice in the WordPress core, once for new user registration, and once when assigning an user to a blog (in multisite installations), so an appropriate string is passed through $operation which we check and handle only when it’s equal to ‘add-new-user‘ (line 7).

The $year variable is set in exactly the same way as it was set in crf_registration_form() earlier. Same thing with the <input> field, except for the CSS class name (we re-use what WordPress provides). I’m repeating these as the wrapping elements are now different (following the WordPress format, we now display the field inside a table). We could of course refactor the input element so as not to repeat ourselves, but it would add some unnecessary complexity for the purposes of this tutorial. Finally, the heading on line 15 is not required, but is a nice touch nonetheless.

Go to your Dashboard → Users → Add New, and like previously, you can now see the field, however there’s no validation, no sanitization, and no saving happening at this point.

Validating the field

In order to validate our field, we’re going to use the ‘user_profile_update_errors‘ action.

add_action( 'user_profile_update_errors', 'crf_user_profile_update_errors', 10, 3 );
function crf_user_profile_update_errors( $errors, $update, $user ) {
	if ( $update ) {
		return;
	}

	if ( empty( $_POST['year_of_birth'] ) ) {
		$errors->add( 'year_of_birth_error', __( '<strong>ERROR</strong>: Please enter your year of birth.', 'crf' ) );
	}

	if ( ! empty( $_POST['year_of_birth'] ) && intval( $_POST['year_of_birth'] ) < 1900 ) {
		$errors->add( 'year_of_birth_error', __( '<strong>ERROR</strong>: You must be born after 1900.', 'crf' ) );
	}
}

Again, the validation bit (lines 7-12) is exactly the same as before, therefore it can be refactored, but let’s focus on the differences instead. The ‘user_profile_update_errors‘ action provides three parameters, $errors, $update and $user, that hold the errors, an update flag and a WP_User object respectively. In our case, $update contains a falsey value (and true if the code is reached by the profile update page), and the WP_User object has most of its properties blank, as there’s not actual user at this time. Since we don’t have any plans on allowing updates on the year of birth, lines 3-5 make sure we only proceed if $update is false.

Note that ‘user_profile_update_errors‘ is an action, in contrast with the earlier ‘registration_errors‘ which is a filter, and therefore we don’t need to return anything. In this case, $errors is passed by reference by the caller, so our $errors and the caller’s $errors is actually the same instance.

Now, if you want to test that validation works, you’ll to provide a username and an email, as those fields also have JavaScript validation and the form will not be submitted if they are empty. Alternatively, you can temporarily disable JavaScript on your browser.

Sanitizing and saving the field

While a valid year will pass validation and the user will be created, the year will not get saved at this point. We need this:

add_action( 'edit_user_created_user', 'crf_user_register' );

The ‘edit_user_created_user‘ action is our equivalent to the previously used ‘user_register‘ action. Both actions pass the $user_id as their first parameter. ‘edit_user_created_user‘ also passes a $notify string parameter (as in the last parameter of wp_new_user_notification()) but since we don’t do anything with it, we can re-use our crf_user_register() by hooking it without any modifications.

You guessed it. You can now register a new user, and their year of birth will persist.

Profile display

All this hard work, and we still can’t see a user’s year of birth. Let’s fix it!

add_action( 'show_user_profile', 'crf_show_extra_profile_fields' );
add_action( 'edit_user_profile', 'crf_show_extra_profile_fields' );

function crf_show_extra_profile_fields( $user ) {
	?>
	<h3><?php esc_html_e( 'Personal Information', 'crf' ); ?></h3>

	<table class="form-table">
		<tr>
			<th><label for="year_of_birth"><?php esc_html_e( 'Year of birth', 'crf' ); ?></label></th>
			<td><?php echo esc_html( get_the_author_meta( 'year_of_birth', $user->ID ) ); ?></td>
		</tr>
	</table>
	<?php
}

The HTML markup inside crf_show_extra_profile_fields(), is again similar to crf_admin_registration_form() but instead of displaying an input field, we’re just printing the value.

Both the ‘show_user_profile‘ and ‘edit_user_profile‘ actions are exactly the same (pass the user as a parameter) and fire at the same point, but on slightly different cases. ‘show_user_profile‘ fires when you’re viewing your own profile page (or any user their page, for that matter), while ‘edit_user_profile‘ fires when you’re viewing another user’s profile (or any user that has the capabilities to do so). You could leverage this difference to, for example, create a scratchpad that only you can see, or a user rating feature where the user can’t vote for himself but see the ratings instead.

Enough with the examples. Make sure you create a user, and of course provide a year of birth. The system won’t let you proceed without it anyway! Then go to Dashboard → Users and Edit your new user. Scroll down, and voila!

Screenshot of the user profile screen showing the year of birth.

User profile screen showing the year of birth.

Conclusion

That was it! With just a few lines of code, we managed to add a required registration field, and learned a whole lot in the process. Some of the hooks can also be useful by themselves, allowing to further restrict registration criteria based on existing fields. Can an existing plugin do that? Perhaps. Can the ready-made plugin be as flexible and expressive as your code? Probably not!

What do you think? Did you find this tutorial useful? Do you have any questions? Leave a comment and let’s discuss!

The full code can be found in GitHub.

Subscribe to our newsletter.

Get fresh WordPress content straight into your inbox. We hate spam more than you do.

6 comments

  1. B. JayaKrishnan says:

    Is there way to display custom fields on top

    1. Anastis Sourgoutsidis says:

      Unfortunately, no.
      As I mentioned in the post, the ‘register_form’ action is the only one included in the form, so we don’t really have any flexibility on positioning our fields.

      You can however build your own registration screen from scratch.

  2. D. Jansen says:

    Just what I need, nice and slick.
    However, the only thing I’m not able to do is edit the field in the backend, after the user is created.
    Of course date of birth is not something that can change overnight, but mistakes can be made, and it would be helpful for other fields. I’ve tried to implement this myself, but the only “new lines” that are being saved, are the lines in my php_error.log!

  3. D. Jansen says:

    And just as I left my, still pending, comment/question on updating in the backend, I succeeded myself. Just needed to use the “edit_user_profile_update” hook.

    1. LD says:

      Would you mind expanding on this? I need to be able to update existing users as well, but can’t figure it out

      1. Anastis Sourgoutsidis says:

        Take a look at How to add a custom user field in WordPress. It’s what you need.

Leave a Reply to Anastis Sourgoutsidis Cancel reply

Your email address will not be published. Required fields are marked *