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.

11 comments

  1. Hi,

    Im just trying to figure out how to change this from a number field to just a plain text one. I’ve tried and it works on the registration page but in the admin I’m just seeing the result 0.

    Any idea how to acheive this?

    1. Anastis Sourgoutsidis says:

      You’ll need to change the validation and sanitization code accordingly.
      For example, you don’t want to sanitize with intval() any more, as it forces the value to become a number (that’s why you get a 0). Try using sanitize_text_field() instead.

  2. Sophie says:

    Hi,

    Thank you for the tutorial, I’ve just managed to put there a text input instead of the numeric one. But can I add there more fields without cutting the loop? How to do it?

    1. Anastis Sourgoutsidis says:

      I’m not sure which loop you are referring to.
      You can add as many fields as you need.

  3. jose ramirez says:

    Hi, I’m learning about the hooks, this article was a little more difficult to grasp than the previous because the access for the edit user page and edit admin page, but other than that it’s an amazing tutorial, I like to change the variable, functions and values when I’m learning something new, just to make sure that I fully understand the concepts, I found that this line of code is not necessary(at least the plugin works as suppose to):

    add_action( ‘edit_user_created_user’, ‘crf_user_register’ );

    I tested as user and as admin, changing the year number, also tested creating new users, and the hook makes no difference(same with the previous tutorial), could explain why allows to save and if its not necessary what other hook is doing the update of info?

    Thank you.

    1. jose ramirez says:

      Hi, also found out that if you remove all the code from line 64 to 106 you get everything working properly, Lol

  4. jose ramirez says:

    Disregard my last message, from line 95 to 104 does the validation.

    I’m starting to like a lot this WP functions:

    user_profile_update_errors
    registration_errors
    update_user_meta
    get_the_author_meta

    so easy to do validations and to update and get data, its amazing.

  5. Kazeem says:

    Great article.
    I am using the code above(edited) but with drop down options. the selected options from the drop down is being saved in the database, but when editing, the display defaults to the first option value, not the selected one.
    Do you mind giving a sample code of how to go about displaying the saved value when the user edits his profile.
    Thanks.

    1. Anastis Sourgoutsidis says:

      Each option needs a call to selected(). The related codex page includes an example on how you’d go about it.
      Hope this helps.

  6. suresh kumar says:

    I am working on a medical reporting site in wordpress, where the requirement is that admin
    can upload a seperate .pdf file to different users’ profile & only the profile owner can download it

    1. Anastis Sourgoutsidis says:

      In this case, a user field is only appropriate for storing the path to the file or other related metadata. File security is not something WordPress does by default (all files in wp-content/uploads/ are downloadable by default), so you’ll need to look into a specialized plugin, or implement a whole bunch of security measures, including proper web server configuration. This StackExchange thread should point you to the right direction.

Leave a Reply

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