Add additional user fields and make them sortable in the user screen

Additional user info

A quick and easy way to add custom fields to your users. Also a bit of MySQL (just a bit) :D

Last week I wasn’t around so I had no time to write a tutorial, but this week we’re back on track :D

In this tutorial I’ll show you how to add custom fields (select, input, you can place even radio buttons in) in your user screen. Say you want to add more information about your users (especially handy when you have a user base on your site and you want to add some additional information about them besides the preexisting ones in WordPress). In our example here, we’ll put in gender, age and some custom category as fields. In your functions.php  file, or other file that you’ve included in your theme, add the extra fields like this

/********* User Custom Fields ***********/
add_action( 'show_user_profile', 'mytheme_extra_profile_fields' );
add_action( 'edit_user_profile', 'mytheme_extra_profile_fields' );

function mytheme_extra_profile_fields( $user ) { 

    $customData = get_user_meta($user->ID);
    $gender = (isset($customData['gender'][0]) && $customData['gender'][0] != '') ? $customData['gender'][0] : '';
    $category = (isset($customData['category'][0]) && $customData['category'][0] != '') ? $customData['category'][0] : '';
    $age = (isset($customData['age'][0]) && $customData['age'][0] != '') ? $customData['age'][0] : '';

	<table class="form-table">
			<th><?php esc_html_e('Gender', 'mytheme'); ?></th>
				<select name="custom_user_field_gender" id="gender">
					<option value="none" <?php selected( $gender, 'none', true); ?>><?php esc_html_e(' -- ', 'mytheme'); ?></option>
					<option value="man" <?php selected( $gender, 'man', true); ?>><?php esc_html_e('Man', 'mytheme'); ?></option>
					<option value="woman" <?php selected( $gender, 'woman', true); ?>><?php esc_html_e('Woman', 'mytheme'); ?></option>
					<option value="other" <?php selected( $gender, 'other', true); ?>><?php esc_html_e('Other', 'mytheme'); ?></option>
			<th><?php esc_html_e('Age', 'mytheme'); ?></th>
				<input type="text" name="custom_user_field_age" id="age" value="<?php echo $age ; ?>" />
			<th><?php esc_html_e('Category', 'mytheme'); ?></th>
				<select name="custom_user_field_category" id="category">
					<option value="none" <?php selected( $category, 'none', true); ?>><?php esc_html_e(' -- ', 'mytheme'); ?></option>
					<option value="super_user" <?php selected( $category, 'super_user', true); ?>><?php esc_html_e('Super User', 'mytheme'); ?></option>
					<option value="regular_user" <?php selected( $category, 'regular_user', true); ?>><?php esc_html_e('Regular User', 'mytheme'); ?></option>
					<option value="nonregular_user" <?php selected( $category, 'nonregular_user', true); ?>><?php esc_html_e('Non Regular User', 'mytheme'); ?></option>

This will add our select and input fields at the bottom of the users screen. We used two hooks here: show_user_profile  and edit_user_profile. Both are standard hooks to use when adding additional fields to your users. Learn more about them here.

Next we need to save our new data

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

function mytheme_save_extra_profile_fields( $user_id ) {

	$userData = array();

	if ( !current_user_can( 'edit_user', $user_id ) )
		return false;

 	if(!empty( $_POST['custom_user_field_gender'] )) {
		$gender = esc_attr( $_POST['custom_user_field_gender'] );
		update_user_meta( $user_id, 'gender', $gender);

 	if(!empty( $_POST['custom_user_field_age'] )) {
		$age = esc_attr( ltrim($_POST['custom_user_field_age'], '0') );
		update_user_meta( $user_id, 'age', $age);

 	if(!empty( $_POST['custom_user_field_category'] )) {
		$category = esc_attr( $_POST['custom_user_field_category'] );
		update_user_meta( $user_id, 'category', $category);


Noticed that we are escaping our data here. This is important, because we’ll need to use this data to run custom queries for sorting, and you don’t want to be able to add a bad code to your database in any way – on save or on custom query. When doing a custom query it’s best to prepare your query, this is the safest method there is, but sometimes you can bypass it if you know that the query won’t be susceptible to outside manipulation.

Single user screen

Single user screen with new fields added

Sorting it all out

Let’s say that we want to show this new cool data we added in the users table, and that we want to sort users by our newly created fields. First we need to add new columns to the existing user columns.

/********* User Custom Columns ***********/
add_filter('manage_users_columns', 'mytheme_add_user_columns');

function mytheme_add_user_columns($columns) {
    $columns['gender']   = esc_html__('Gender', 'mytheme');
    $columns['age']      = esc_html__('Age', 'mytheme');
    $columns['category'] = esc_html__('Category', 'mytheme');
    return $columns;

add_action('manage_users_custom_column',  'mytheme_show_user_columns_content', 10, 3);

function mytheme_show_user_columns_content($value, $column_name, $user_id) {
    $user = get_user_meta( $user_id );
	if ( 'gender' == $column_name ){
		$col_array = ( isset( $user['gender'][0] ) ) ? $user['gender'][0] : '' ;
		return ($col_array == 'none' ) ? ' -- ': $col_array;
	if ( 'age' == $column_name ){
		$col_array = ( isset( $user['age'][0] ) ) ? $user['age'][0] : '--' ;
		return $col_array;
	if ( 'category' == $column_name ){
		$col_array = ( isset( $user['category'][0] ) ) ? $user['category'][0] : '' ;
		return ($col_array == 'none' ) ? ' -- ': $col_array;
    return $value;

Great, so now to sort them

/********* Sortable User Columns ***********/
add_filter( 'manage_users_sortable_columns', 'mytheme_user_sortable_columns' );

function mytheme_user_sortable_columns( $sortable_columns ) {
   $sortable_columns['gender']   = 'Gender';
   $sortable_columns['age']      = 'Age';
   $sortable_columns['category'] = 'Category';
   return $sortable_columns;

add_action('pre_user_query', 'user_column_orderby');

function user_column_orderby($user_search) {
    global $wpdb, $current_screen;

	if (isset($current_screen->id) && 'users' != $current_screen->id) {

    $vars = $user_search->query_vars;

    if('Gender' == $vars['orderby']) {
        $user_search->query_from .= " INNER JOIN {$wpdb->usermeta} m1 ON {$wpdb->users}.ID=m1.user_id AND (m1.meta_key='gender')";
        $user_search->query_orderby = ' ORDER BY UPPER(m1.meta_value) '. $vars['order'];
    } elseif ('Age' == $vars['orderby']) {
        $user_search->query_from .= " INNER JOIN {$wpdb->usermeta} m1 ON {$wpdb->users}.ID=m1.user_id AND (m1.meta_key='age')";
        $user_search->query_orderby = ' ORDER BY UPPER(m1.meta_value) '. $vars['order'];
   	} elseif ('Category' == $vars['orderby']) {
        $user_search->query_from .= " INNER JOIN {$wpdb->usermeta} m1 ON {$wpdb->users}.ID=m1.user_id AND (m1.meta_key='category')";
        $user_search->query_orderby = ' ORDER BY UPPER(m1.meta_value) '. $vars['order'];

Usually you’ll see articles that explain how to sort custom columns, whether they are used for custom post types, or regular posts. Like for instance this one. And that’s great. If you have alphabet or numerical sorting. But what if you have something other? Like our gender column? Then things don’t sort as you want them to. Btw the above code is fine to use on custom column sorting in posts or CPT as well.

What we used here is  pre_user_query hook. You can read more about it here. It’s kinda analogous to pre_get_posts hook, but for users. Basically we’re tapping into the sorting query (that already exists) and we’re modifying it a bit with our custom query. For this you’ll need a bit of MySQL knowledge.

What we’re doing is joining the usermeta table with the users table where the user id matches in both tables, and we’re watching our meta keys we’ve added, and then just use the query_orderby to order our meta values for the specified keys.

Bonus code

For the little extra code I’ll add the export to .csv (comma separated values). This comes in handy if you want to create a mailing list or manipulate your users in excel (or excel like programs)

/********* Export to csv ***********/
add_action('admin_footer', 'mytheme_export_users');

function mytheme_export_users() {
    $screen = get_current_screen();
    if ( $screen->id != "users" )   // Only add to users.php page
	<script type="text/javascript">
		jQuery(document).ready( function($)
			$(' .clear, .tablenav.bottom .clear').before('<form action="#" method="POST"><input type="hidden" id="mytheme_export_csv" name="mytheme_export_csv" value="1" /><input class="button button-primary user_export_button" style="margin-top:3px;" type="submit" value="<?php esc_attr_e('Export All as CSV', 'mytheme');?>" /></form>');

add_action('admin_init', 'export_csv'); //you can use admin_init as well

function export_csv() {
    if (!empty($_POST['mytheme_export_csv'])) {

		if (current_user_can('manage_options')) {
			header("Content-type: application/force-download");
			header('Content-Disposition: inline; filename="users'.date('YmdHis').'.csv"');

			// WP_User_Query arguments
			$args = array (
				'order'          => 'ASC',
				'orderby'        => 'display_name',
				'fields'         => 'all',

			// The User Query
			$blogusers = get_users( 'orderby=nicename&order=ASC' );
			// Array of WP_User objects.
			foreach ( $blogusers as $user ) {
				$meta = get_user_meta($user->ID);
				$role = $user->roles;
				$email = $user->user_email;

				$first_name = ( isset($meta['first_name'][0]) && $meta['first_name'][0] != '' ) ? $meta['first_name'][0] : '' ;
				$last_name  = ( isset($meta['last_name'][0]) && $meta['last_name'][0] != '' ) ? $meta['last_name'][0] : '' ;

				$gender     = ( isset($meta['gender'][0]) && $meta['gender'][0] != '' ) ? $meta['gender'][0] : '' ;
				$age        = ( isset($meta['age'][0]) && $meta['age'][0] != '' ) ? $meta['age'][0] : '' ;
				$category   = ( isset($meta['category'][0]) && $meta['category'][0] != '' ) ? $meta['category'][0] : '' ;

				echo '"' . $first_name . '","' . $last_name . '","' . $email . '","' . $gender . '","' . $age . '","' . $category . '","' . ucfirst($role[0]) . '"' . "\r\n";


I’ve added the blue button for export in the tablenav top and bottom part of the users screen. The whole thing looks a bit like this

Users Screen

Users screen with all the new info and export button

And this is it, simple way to add new info and fiddle with it the way you like. This code is cool, as it can be adjusted for regular sorting, and adapted to your needs :) Hope you find it useful and if you have any question, post them in the comments below. Happy coding!

Also published on Medium.

Join the Discussion