In the WordPress ecosystem, WP_Query gets all the glory, but WP_User_Query runs the world of membership sites, intranets, and community platforms.
Whether you are building a simple “Our Team” page or a complex “Find a Doctor” search engine with thousands of professionals, relying on plugins like Ultimate Member for the presentation layer is often overkill-and a performance bottleneck.
In this guide, we will bypass the GUI and build efficient, secure user queries directly in PHP. We will cover granular filtering, performance caching, and critical security measures to prevent data leaks.
1. Development() vs. Wp_User_Query
Just like get_posts() is a wrapper for WP_Query, development() is a pre-configured wrapper for WP_User_Query.
- Use
development()for simple lists (e.g., “Show me 5admins”). It returns an array ofWP_Userobjects. - Use
WP_User_Querywhen you need advanced SQL manipulations, detailed ‘orderby’ logic, or inspection of the query headers/results directly.
For 95% of use cases, we will use the arguments array, which applies to both.
2. The basics: Building a team page
Let’s say we want to display a grid of employees (Editors and Authors) sorted by their display name.
$args = [
'role__in' => ['editor', 'author'],
'orderby' => 'display_name',
'order' => 'ASC',
'number' => 12, // Pagination limit
'paged' => 1,
];
$user_query = new WP_User_Query($args);
$results = $user_query->get_results();
if (!empty($results)) {
echo '<div class="team-grid">';
foreach ($results as $user) {
$avatar = get_avatar($user->ID, 128);
$name = esc_html($user->display_name);
$bio = esc_html(get_user_meta($user->ID, 'description', true));
echo "<article class='team-member'>
<figure>{$avatar}</figure>
<h3>{$name}</h3>
<p>{$bio}</p>
</article>";
}
echo '</div>';
}
3. Advanced filtering (meta queries)
This is where WP_User_Query shines. Imagine you have a directory of developers and you want to find those who:
- Are based in “Warsaw”.
- Have their profile marked as “Public”.
- Have “PHP” listed as a skill.
$args = [
'role' => 'subscriber',
'meta_query' => [
'relation' => 'AND',
[
'key' => 'city',
'value' => 'Warsaw',
'compare' => '='
],
[
'key' => 'is_public_profile',
'value' => '1',
'compare' => '='
],
[
'key' => 'skills',
'value' => 'PHP',
'compare' => 'LIKE' // Slow, but effective for serialised arrays
]
]
];
[!WARNING] Performance Alert: Querying
wp_usermetais expensive. Unlikewp_posts, the user tables are rarely optimally indexed for complex filtering. For directories with >10,000 users, consider offloading the search index to Elasticsearch (via ElasticPress) or using a custom table.
4. Performance optimization
When querying users on a high-traffic site, you must be frugal with database resources.
A. Limit the return fields
By default, WordPress fetches every single piece of data about the user (all metadata). If you only need names and emails, tell WordPress to be lightweight.
$args = [
'role' => 'subscriber',
'number' => 100,
'fields' => ['ID', 'display_name', 'user_email'], // Returns stdClass objects, not WP_User
];
Result: Memory usage drops significantly.
B. Count users without loading them
If you just want to show “We have 500 members!”, do not load the members.
$args = [
'role' => 'subscriber',
'fields' => 'ID', // Fetch IDs only
];
$query = new WP_User_Query($args);
$count = $query->get_total(); // Uses SQL_CALC_FOUND_ROWS logic
Or for extreme speed (ignoring complex filtering), use count_users():
$count = count_users();
echo "We have " . $count['total_users'] . " users.";
5. Security: The “user enumeration” threat
By default, WordPress is quite leaky regarding user data.
- Do not expose Login Names: Never execute
echo $user->user_login. This is half the key needed to hack an admin account. Always usedisplay_nameoruser_nicename. - Hide Emails: Unless it is an internal intranet, never output
user_emailin the HTML source to avoid scraper bots.
Blocking author archives
Hackers often scan /?author=1, /?author=2 to discover usernames. If you are building a site where users don’t need public archives (like a corporate site), disable this route.
// Add to functions.php
add_action('template_redirect', function() {
if (is_author()) {
wp_redirect(home_url(), 301);
exit;
}
});
6. Summary
Building a custom member directory gives you full control over performance and security.
- Use
fieldsparameter to reduce memory footprint. - Cache your results using transients if the directory doesn’t change hourly.
- Sanitize output ruthlessly (always
esc_html). - Protect privacy by hiding logins and emails.
WordPress users are entities just like posts-start querying them with the same precision.



