How to add a custom user field in WordPress

WordPress stores user information out of the box, and it cares for the user’s name, email, and some more basic info, it leaves a lot to be desired. For example, social network links are pretty-much required these days, but since social networks come and go on a daily basis, WordPress itself can’t commit to supporting any single one as it may not exist tomorrow. A user’s date of birth is quite important for some websites as well (for example, with age-restricted content), however since a user’s date of birth may be considered confidential or identifying information, it may be illegal (or require special permits) in some countries. Again, WordPress won’t collect this information by itself in order to give us (its users) the freedom to use it no matter of our location. It therefore provides the means to build support for this extra information ourselves.

Building upon our previous tutorial, How to add custom fields to the WordPress registration form, were we collected the user’s year of birth upon registration and displayed it on the user’s profile page, we’ll now learn how to make this information editable so that the user itself (or an admin) can change it, because humans make mistakes all the time, even if it’s their own year of birth! Make sure you read the tutorial before reading this one, as we’ll be re-using and extending its code.

The code presented in this tutorial is also what’s needed for any custom user field that doesn’t need to be in the registration form. Just like the user’s name and surname!

Showing the user field

As mentioned in the registration form tutorial, actions ‘show_user_profile‘ and ‘edit_user_profile‘ are available for adding our own user fields. The former fires when users are seeing/editing their own profile information, while the latter fires when a user (such as an admin) sees/edits another user’s profile. Both actions pass a WP_User object as their sole parameter. Our previous code, which already uses those actions, was this:

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
}

Let’s go ahead and change the plain text year of birth, to an input element, so that it may accept user input.

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 ) {
	$year = get_the_author_meta( 'year_of_birth', $user->ID );
	?>
	<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>
				<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
}

Let’s check our profile page:

Note that I’m testing using a user which was registered in our previous tutorial, so the year of birth was provided during registration. If you’re trying it on a pre-existing user, the year of birth will be empty. It’s okay. Just fill it in and keep following the tutorial.

Validating the field

We can use the ‘user_profile_update_errors‘ action to validate and return errors to our form. It provides three parameters, $errors, $update and $user, that hold the errors, an update flag and a WP_User object respectively. Perhaps you remember that we’ve already used this action, in order to validate the admin-side user registration form (e.g. from an admin). The code we used in the tutorial was:

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' ) );
	}
}

Lines 3-5 checked that if the request was for updating a user (not applicable at that tutorial, since it was about the registration form), and if it was, it just bailed. Well, in this tutorial we want it to apply both on the registration and when updating user fields so we can just remove those lines altogether.

add_action( 'user_profile_update_errors', 'crf_user_profile_update_errors', 10, 3 );
function crf_user_profile_update_errors( $errors, $update, $user ) {
	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' ) );
	}
}

If we wanted to only validate when updating, because for example the field wasn’t present in the registration form, the we would have negate the if() statement instead.

Now, if we try and enter an invalid value, e.g. 1800:

Saving the field

Just like the ‘show_user_profile‘ and ‘edit_user_profile‘ actions mentioned earlier, actions ‘personal_options_update‘ and ‘edit_user_profile_update‘ fire when the “Update profile” button is pressed, when updating your own or another user’s respectively. Both actions pass the modified user’s ID, so we can handle both cases with just one function:

add_action( 'personal_options_update', 'crf_update_profile_fields' );
add_action( 'edit_user_profile_update', 'crf_update_profile_fields' );

function crf_update_profile_fields( $user_id ) {
	if ( ! current_user_can( 'edit_user', $user_id ) ) {
		return false;
	}

	if ( ! empty( $_POST['year_of_birth'] ) && intval( $_POST['year_of_birth'] ) >= 1900 ) {
		update_user_meta( $user_id, 'year_of_birth', intval( $_POST['year_of_birth'] ) );
	}
}

We just need to make sure thing of two things:

  1. The current user has the right to modify the user in question (line 5), and
  2. The field has a valid value. The related code inside the crf_user_profile_update_errors() function, merely appends the error notifications on screen. Execution continues normally though, and if we don’t make sure the value is valid before saving it (line 9), we’re going to end up with bad values, sooner than later.

You can now test that any changes you make to the year of birth field are persisting properly!

Wrapping it up

Due to referring back to the plugin built in the previous tutorial, it may not be clear how you’d go about adding the year of birth just as a user profile field, ignoring any previous code, tutorials, etc. This is how you’d add the field:

<?php
/*
Plugin Name: Custom Profile 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
*/

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 ) {
	$year = get_the_author_meta( 'year_of_birth', $user->ID );
	?>
	<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>
				<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
}

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' ) );
	}
}


add_action( 'personal_options_update', 'crf_update_profile_fields' );
add_action( 'edit_user_profile_update', 'crf_update_profile_fields' );

function crf_update_profile_fields( $user_id ) {
	if ( ! current_user_can( 'edit_user', $user_id ) ) {
		return false;
	}

	if ( ! empty( $_POST['year_of_birth'] ) && intval( $_POST['year_of_birth'] ) >= 1900 ) {
		update_user_meta( $user_id, 'year_of_birth', intval( $_POST['year_of_birth'] ) );
	}
}

And this is what it looks like if it’s included in the plugin that we previously developed on the registration form tutorial:

<?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
*/

/**
 * 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
}

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;
}

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'] ) );
	}
}

/**
 * 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
}

add_action( 'user_profile_update_errors', 'crf_user_profile_update_errors', 10, 3 );
function crf_user_profile_update_errors( $errors, $update, $user ) {
	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' ) );
	}
}

add_action( 'edit_user_created_user', 'crf_user_register' );


/**
 * Back end display
 */

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 ) {
	$year = get_the_author_meta( 'year_of_birth', $user->ID );
	?>
	<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>
				<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
}

add_action( 'personal_options_update', 'crf_update_profile_fields' );
add_action( 'edit_user_profile_update', 'crf_update_profile_fields' );

function crf_update_profile_fields( $user_id ) {
	if ( ! current_user_can( 'edit_user', $user_id ) ) {
		return false;
	}

	if ( ! empty( $_POST['year_of_birth'] ) && intval( $_POST['year_of_birth'] ) >= 1900 ) {
		update_user_meta( $user_id, 'year_of_birth', intval( $_POST['year_of_birth'] ) );
	}
}

 

Subscribe to our newsletter.

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

Leave a Reply

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