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.
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.
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 ;).
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'
)
)
) );
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.
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.
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
unlessWP_Query
orWP_User_Query
absolutely cannot perform what I need.Tom, how would you go about adding pagination to this?
Check out this post. It should have everything you need :).
Tom, the link does not work :(
This has been fixed – thanks for the heads up!
Looks like the link re-broke itself, it’s taking me here right now:
http://wp_query-pagination/
Which link? All seem to be okay for me.
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.
Thanks Kim!
Thanks, this is really handy!
Glad to hear it!
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!
Sure thing, Ksenia!
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.
Thanks for this link, kilbot. Good to know!
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
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’,
) );
Thanks for keeping this post current, John – appreciate that!
Tom, any idea how pagination would work on this?
Yep. See this post and see this Codex article and you should be good to go.
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! :)
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.
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.
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!
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.
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
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.
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.
Thanks, this problem was solved by Michael’s post a couple posts back up.
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.
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.
Yes, many thanks, I managed to get it working … thanks everyone for the pointers!
Jeremy
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’,
?
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 thewp_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 :).