Software Engineering in WordPress and Musings on the Deep Life

Using WP_User_Query To Retrieve Users Across Multiple Roles

I’ve covered WP_User_Query in a previous post. In short, WP_User_Query is the preferred method for retrieving user information from the database when working with custom queries.

One of the shortcomings of this API method is that it doesn’t allow you to query across multiple roles. So, for example, if you want to retrieve users that meet a certain criteria but may span across multiple roles – say editors and administrators – the API doesn’t support it.

That said, there is a simple strategy that can be used with WP_User_Query to retrieve users across multiple roles.

A Real World Scenario

Before setting up the query, we need to define a scenario in which this may be useful. In one of my recent projects, all users accounts had a piece of meta data associated with them.

Specifically, users had a meta key of ‘featured’ and a value of ‘yes’ or a value of ‘no.’ In code, it worked something like this:

add_user_meta( $user_id, 'featured', 'yes' );

From there, users could either be set as administrators or as editors and I needed to pull back five featured users from each role and then render their display name on the frontend.

Ultimately, the final loop looked something like this:

foreach( $users as $user ) {
  echo $user->display_name;
} // end foreach

Simple, right? But there’s a bit that actually goes into collecting all of the users into that single array.

WP_User_Query and Multiple Roles

Given the state of some of the other API methods, I think that it’s a reasonable assumption that you could setup a call to WP_User_Query like this:

$editor_query = new WP_User_Query(
	array(
		'role'			    =>	array( 'editor', 'administrator' ),
		'meta_key' 		  => 'featured',
		'meta_value'	  =>	'yes',
		'meta_compare'	=>	'=',
		'number'		    =>	5
	)
);

But that’s not the case. The role parameter actually only accepts a single string, not an array.

Instead, you have to actually setup two separate queries and then merge the results into a single array:

// get the featured editors
$editor_query = new WP_User_Query(
	array(
		'role'			    =>	'editor',
		'meta_key' 		  => 'featured',
		'meta_value'	  =>	'yes',
		'meta_compare'	=>	'=',
		'number'		    =>	5
	)
);
$editors = $editor_query->get_results();

// get the featured admins
$administrator_query = new WP_User_Query(
	array(
		'role'			    =>	'administrator',
		'meta_key' 		  => 'featured',
		'meta_value'	  =>	'yes',
		'meta_compare'	=>	'=',
		'number'		    =>	5
	)
);
$admins = $administrator_query->get_results();

// store them all as users
$users = array_merge( $admins, $editors );

Obviously, it’s not the most elegant solution since you have to setup several independent queries, but it does provide a straightforward way of querying users across multiple roles, and then accessing them all from a single, independent query.

As usual, I’m always interested in how others address this issue especially if there’s a more optimal solution so let me know in the comments.

39 Comments

  1. Elliott Richmond

    it just so happens I was after this very thing recently, in the end I used get_users and removed admins with jquery, this is a much better solution thanks Tom.

    • Tom McFarlin

      Of course, Elliot – glad to help!

      When it comes to stuff like this, I generally think it’s better to do as much work on the server side since any clever end user that’s comfortable with a debug console could end up displaying some information that you intended to hide.

      In your case, it’s nothing too drastic though, so maybe I’m being a bit facetious ;).

  2. Grant Kinney

    I found this solution by Andy Adams on the wordpress stackexchange site. It uses a meta query to pull users by their associated capabilities from the usermeta table. I think it works quite well for this task.


    global $wpdb;

    $user_query = new WP_User_Query( array(
    'meta_query' => array(
    'relation' => 'OR',
    array(
    'key' => $wpdb->prefix . 'capabilities',
    'value' => 'role_one',
    'compare' => 'like'
    ),
    array(
    'key' => $wpdb->prefix . 'capabilities',
    'value' => 'role_two',
    'compare' => 'like'
    )
    )
    ) );

    • Tom McFarlin

      Andy’s a great developer so thanks for sharing this. Love having a couple of different ways to perform the same task.

      The only thing with the code above is that I dislike the concatenation of the $wpdb->prefix with the string in the array. Honestly, it’s not that big of a deal – it’s a matter purely of personal preference. I’m picky like that :).

      Nonetheless, I do appreciate you sharing this snippet as it does provide a bit of a more concise way of doing exactly this.

  3. Julien

    Thanks a lot for this method! It was really helpful to me. Also, I tried both your method and Andy’s: yours is way faster.

    • Tom McFarlin

      Thanks for sharing this Julien!

      I can’t necessarily say why it’s faster without digging deeper and benchmarking, but I suspect that it has to do with the query optimization that comes with using WP_Query where as Andy’s method is using a combination of that and the $wpdb object.

      My general rule of thumb is to avoid $wpdb unless WP_Query or WP_User_Query absolutely cannot perform what I need.

  4. Scott Mackenzie

    Tom, how would you go about adding pagination to this?

  5. Kim Joy Fox

    Thanks for the clarification! would love if they added in your solution to WordPress code, but until then, I like the clarity of your coding.

  6. Shaan Nicol

    Thanks, this is really handy!

  7. Ksenia

    Tom, thanks a lot for this very useful article! I was after that! And now thanks to you I know that I’m not crazy…and querying users by several roles just impossible :) In some way. And thanks for the advice of using array’s merging method!

  8. kilbot

    There is a performance issue with querying multiple meta keys using the OR relation. It seems separating the queries and merging the results is the best solution at the moment performance-wise.

    You can read more about the issue here: https://core.trac.wordpress.org/ticket/24093 .. hopefully it will be patched soon.

    • Tom

      Thanks for this link, kilbot. Good to know!

  9. Ben

    I’ve worked out a way to do an exclude type query that will show me all users aside from subscribers.

    I’ve updated the author filter metabox plugin from Frank Bueltge

    https://gist.github.com/benklocek/8cae13427ed7760cb76c

  10. John Rom

    I know this is an old post, but it still turns up pretty early in search results related to this subject. I would like to point out that anybody who uses Andy’s or Ben’s “User Meta” method will run into an issue in the event they have two roles which contain one another in the name.

    For example, my site has both “administrator” and “regional-administrator”. If I tried to use either of those queries for ‘administrator’, it would find it within the wp_capabilities meta value:

    a:1:{s:22:”regional-administrator”;b:1;}

    If you are using something like Ben’s, you should wrap it in double quotes. If there are dozens of roles or an unknown number of roles, and you’d like to exclude one, meta queries are the way to go. However, if you want specific roles, use the array_merge method, because an OR in meta_query is much more expensive.

    So the exclusion code should go: (note the double quotes in ‘value’)

    $meta_query = array( array(

    ‘key’ => $wpdb->prefix . ‘capabilities’,

    ‘value’ => ‘”‘ . $role . ‘”‘,

    ‘compare’ => ‘like’,

    ) );

    • Tom

      Thanks for keeping this post current, John – appreciate that!

  11. Varun Gupta

    Tom, any idea how pagination would work on this?

  12. Michael

    Hey Tom,

    I ran into this article myself recently, as I was searching up a way to target multiple roles, and in the process of researching, had found a discussion on StackExchange that proved interesting:

    http://wordpress.stackexchange.com/questions/39315/get-multiple-roles-with-get-users/209593#209593

    The role__in parameter mentioned there would be a way of achieving your desire for a more elegant code snippet. :)

    The first code block you offered in your article would now be possible, just by changing ‘role’ to ‘role__in’, tested it out before posting here:

    $editor_query = new WP_User_Query(

    array(

    ‘role__in’ => array( ‘administrator’, ‘editor’ ),

    ‘meta_key’ => ‘featured’,

    ‘meta_value’ => ‘yes’,

    ‘meta_compare’ => ‘=’,

    ‘number’ => 5

    )

    );

    $editors = $editor_query->get_results();

    foreach( $editors as $editor ) {

    echo $editor->display_name;

    } // end foreach

    Hope this helps, keep up the good work Tom! :)

    • Tom

      Thanks for adding this to the comment. All source code is eventually going to need to be refined and improved over time so it’s nice to have this available in the comments.

    • Jeremy

      Thanks Michael for this, it solves the problem I had trying to get info on multiple roles. I posted a comment yesterday asking how to get rid of duplicates in the case where you have to run a separate query per role and then use merge_array. But your code does it all in one, very neat.

      The issue I am left with is how to generate the roles on the fly from selections made in a checkbox form.

      So instead of the code for the ‘role__in’ line being hardcoded for the (maximum) 4 roles I may want to search for, I need to allow someone to select just two roles say (using a checkbox in a form), and then the ‘role__in’ line needs to be populated with a variable.

      i.e. :

      This works now for me:

      ‘role__in’ => array(‘role1’, ‘role2’, ‘role3’, ‘role4’)

      But someone may just want users associated with role2 and role4 say. Hence I would want to create a variable such as:

      $var1 = ” ‘role1’, ‘role2’ “;

      And then have a line like this:

      ‘role__in’ => array($var1)

      For some reason I just can’t get it to work. Is it possible to pop a variable in that query? I’ve tried reading the PHP online help for arrays and couldn’t see anyone do this.

      Thanks,

      Jeremy.

      • Michael

        Hey Jeremy,

        The role__in parameter must be fed an array with a role specified at each index. This here:

        $var1 = "'role1', 'role2'";

        Would actually insert a single string into the array, like this:

        'role__in' => array("'role1', 'role2'"),

        So role_in’s stuck, as it doesn’t have information it can work with.

        This, on the other hand:

        $var1 = array( 'role1', 'role2', 'role3', 'role4' );

        Would give role__in something it could work with:

        'role__in' => $var1,

        Now, since you’re only wanting to specify certain roles in the array, based on what a user has chosen, that’s going to take more work. I don’t know how this form of yours returns it’s data on what roles a user has chosen, but know that the data provided from it will need to be fed to an array (the array_push method can be your friend here). And then, said array would need to be fed to role_in, as specified earlier.

        Consider the following:

        $var1 = array();

        array_push($var1, 'administrator', 'editor');

        This would work with my earlier code example:

        $var1 = array();

        array_push($var1, 'administrator', 'editor');

        $editor_query = new WP_User_Query(

        array(

        'role__in' => $var1,

        'number' => 5

        )

        );

        $editors = $editor_query->get_results();

        foreach( $editors as $editor ) {

        echo $editor->display_name;

        } // end foreach

        You’d probably be looking to have a for loop wrapped around the array_push() call here, with each role from your form submission added to the array one by one. Like I said earlier, without knowing what the form’s giving us to work with, it’s kinda tough to specify what should be done here.

        That said, hope this provides some added insight on the matter!

        • Jeremy

          Hi Michael,

          Many thanks for your reply … since I posted I have been hovering around array_push as it just sounds like what I needed … and I eventually got it working, so you were correct that it would be my friend! Looks like I managed to not properly add data into the array before I used it. Sometimes you just can’t see the wood for the trees eh.

          Thanks so much for the support on this site, knowing there are willing and able minds out there makes a huge difference in those dark, on your own, at the keyboard moments!

          Jeremy.

  13. Vlado

    Maybe via SQL:

    SELECT SQL_CALC_FOUND_ROWS * FROM {$wpdb->prefix}_users

    LEFT JOIN {$wpdb->prefix}_usermeta AS mt1 ON ( {$wpdb->prefix}_users.ID = mt1.user_id )

    WHERE ( mt1.meta_key = ‘{$wpdb->prefix}_capabilities’ AND mt1.meta_value REGEXP ‘role1|role2’ )

    GROUP BY {$wpdb->prefix}_users.ID

    • Tom

      Usually one of the availbale APIs can achieve what you need without using direct SQL, but if you end up using it, make sure you’re leveraging the $wpdb API.

      Specifically, make sure that you’re parameterizing your queries, and that you’re sanitizing the incoming data properly so to avoid any injection attempts.

  14. Jeremy

    Hi,

    I am looking for a solution in this area … I have some code that follows your opening code suggestion up to the point where the array_merge takes place to get users from more than one role.

    If I do a foreach loop for each individual array I can access the contents.

    If I do an array_merge on the arrays I can similarly do a foreach loop and access the contents.

    However, I have duplicates in there and could not work out how to remove the duplicates. and then loop through a foreach to print out the user’s details.

    I tried code like:

    $users2 = array_unique($users)

    … and then tried to run the same foreach loop on $users2 … but I get an error message “Catchable fatal error: Object of class WP_User could not be converted to string”

    Any advice?

    Thanks,

    Jeremy.

    • Jeremy

      Thanks, this problem was solved by Michael’s post a couple posts back up.

  15. Jeremy

    There’s still something I’m missing on the code I’m using, think I’ve been staring at it too long now & can’t see the wood for the trees!

    The code below uses the suggestion above to use ‘role__in’ and it works:

    | $array_var=array(‘role1’, ‘role2’, ‘role3’, ‘role4’);

    | $args5 = array(

    | ‘role__in’ => $array_var,

    | ‘orderby’ => ‘user_nicename’,

    | ‘order’ => ‘ASC’

    | );

    |
    | $all_users = get_users($args5);

    But I would like to have the bit in brackets for $array_var created dynamically as a variable. i.e. have some code with a checkbox in a form allows people to select which roles they wish to get info for and then those roles are put into a variable like this:

    | $var=”‘role1’, ‘role2’, ‘role3’, ‘role4′”

    Then use $var and proceed as before:

    | $array_var=array($var);

    | $args5 = array(

    | ‘role__in’ => $array_var,

    | ‘orderby’ => ‘user_nicename’,

    | ‘order’ => ‘ASC’

    | );

    |
    | $all_users = get_users($args5);

    Except it returns an empty result set.

    Any ideas?

    Appreciate any assistance.

    Thanks,

    Jeremy.

  16. Bryan Hoffman

    Jeremy, it looks like your $var is already an array. Try it like this:

    `
    <?php

    $args5 = array(

    ‘role__in’ => $var,

    ‘orderby’ => ‘user_nicename’,

    ‘order’ => ‘ASC’

    ); ?>

    `

    Also thanks to everyone who’s contributed to this post. Glad to have finally stumbled upon an answer to the multiple user role query.

    • Jeremy

      Yes, many thanks, I managed to get it working … thanks everyone for the pointers!

      Jeremy

  17. Tom Whitaker

    Hey Tom,

    I scanned the threads above but I didn’t fully understand everything. I’m looking to write a WP_User_Query that will fetch all users except super admins. Can I use something like:

    ‘role__not_in’ => ‘super admin’,

    ?

    • Tom

      Super Admins are those who have access to all sites as part of a network sites. This is different than all of the other roles because the other roles have slugs such as administrator and contributor and subscriber.

      Unfortunately, I don’t do any work with multisites; however, I do know you could look in the wp_usermeta table at the wp_capabilities key and then see what value is stored.

      For example, for a standard installation, you’re going to see wp_capabilities' set equal to, perhaps,a:1:{s:6:”author”;b:1;}`, if they are an author.

      Since you’re looking at user metadata, you may be able to run a meta query like this and use the given key and value for Super Admins. But this is just via deduction – not experience so good luck :).

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

© 2023 Tom McFarlin

Theme by Anders NorenUp ↑