I'm trying to create an archive of users. My first thought was to use WP_User_Query inside a page to create a secondary loop.

$wp_user_query = new WP_User_Query( $args );
// Loop here.

This seemingly works well until I try to visit page 2, 3, 4 and so on.

The main query (which handles pagination) is for the page so there is just 1 "post" in the main query. This means there's a first page:

  • example.com/page-name/

... but none of the following exist in the eyes of the main query:

  • example.com/page-name/page/2/
  • example.com/page-name/page/3/
  • example.com/page-name/page/4/

As you can see, my approaching of using a secondary loop isn't good because pagination fails.

My question

Can the main loop be modified to query for users, not posts? If so, how can this be done?

If you think this is a bad approach, please say why (I'd be happy to hear alternatives).

1 Answer 1


Custom GET pagination parameter

We can use a custom GET pagination parameter, e.g. upage or probably more unique as wpse-user-page, to use with your WP_User_Query. That way you avoid affecting e.g. the main post query.

Generate pagination links with pageinate_links()

We can use paginate_links() to generate the pagination:

$args = array(
    'base'         => '%_%',
    'format'       => '?upage=%#%',
     'total'       => $total_pages,  // e.g. ceil( $total_users / $number ); 
     'current'     => $current_page, // from our custom GET pagination parameter

$paginate_html = sprintf( 
    '<div class="users-pagination">%s</div>', 
    paginate_links( $args ) 

Where to display the user archive?

We can e.g. display it on a given page, using a shortcode or a custom page template.

Prettify the user archive's pagination url

Here are few approaches to prettify the custom GET pagination parameter:

A) Add it as a custom endpoint to pages with:

add_rewrite_endpoint( 'upage', EP_PAGES );

by hooking this into the init action and then after saving permalinks, access the user archive from all pages:

... etc ...

Then we fetch the current page with:

get_query_var( 'upage', 1 );

B) If we want to target a given page slug (e.g. authors) we can hook the following into the init hook:


and then register upage as a query variable:

add_filter( 'query_vars', function( $vars ) {
    $vars[] = 'upage';
    return $vars;
} );

After saving permalinks, we access the user archive from:


C) If we could also override the page url part, that corresponds to the paged query variable, with:


where we register upage as a query variable as before.

Then after saving permalinks, we can access the user archive from:


D) We could also just use the page query variable for the authors page and access the user archive with:


where the current page is:

get_query_var( 'page', 1 );

Note that this would interfere with the content pagination, but there's no limit check for that query variable.

E) We could simulate how the comments pagination is handled in core, that uses a rewrite like this one:


where cpage is the corresponding comment pagination query variable for the ([0-9]{1,}) match.

Alternatively, one could also consider using the REST API with javascript approach. The core ships with the wp-api Backbone client library that can handle e.g. users collection.

Hope it helps!

  • This definitely helps, thanks! I have just a question. Would the custom GET pag param mean URLs need to look like example.com/pagename?wpse-user-page=2? If so, any suggestion for making them look pretty? Maybe that should be another question though :) May 28, 2017 at 0:13
  • I updated the answer with some ideas how to prettify it @henrywright
    – birgire
    May 28, 2017 at 11:00
  • Reading through, I think C is a good, and perhaps the best, approach. The only problem with C is URLs such as example.tld/authors/page/2831323/ and example.tld/authors/page/213236125/ don't return a 404. This could mean a lot of duplicate content? May 28, 2017 at 11:34
  • The same applies to comments on a page, you wouldn't get a 404 for comments-page-9999. The embedded users' list would then just display e.g. no users found. Note also that page content pagination works also like this, check e.g. any WP page with any content page, here's an example with /1234 added to the page url. So that's a built-in content duplication for millions of sites ;-)
    – birgire
    May 28, 2017 at 11:52
  • 1
    Otherwise I was playing with the posts_pre_query filter to map user objects from $users = WP_User_Query() (fed with the offset based on paged) to posts objects and adjust the found_posts to count( $users->results ) and max_num_pages to $users->total_users. Some parts works, but e.g. the pagelinks would need to be modified. But I don't think I would recommend this approach ;-)
    – birgire
    May 28, 2017 at 11:52

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.