Working pagination for custom WP_Query objects

⏲️ Time: 2 mins

If you have ever created a custom WP_Query object, chances are you have potentially needed to also provide pagination for the resulting loop and display of posts. Sadly, WordPress core does not make this the easiest to achieve, but it is possible depending on which pagination-based functions you use. This tutorial aims to show how to achieve functional pagination without getting very “dirty” in your code.

What I mean by “dirty” in this case is having to utilize the global $wp_query variable name. Many of the following functions rely on that specifically, without allowing custom setting overrides. If you have ever come across a tutorial that brings up setting the original $wp_query object to a temporary variable, and then setting $wp_query to null before re-purposing it, then you know what I am referring to here.

Pagination functions to use

The biggest key is the ability to provide a “max pages” value. The WordPress core source code shows that the only two functions that provide the ability to custom set those are:

  • get_next_posts_link()
  • get_previous_posts_link()

They have a second parameter available that allows you to set your own max_num_pages value, instead of relying on the $wp_query variable name.

Imagine your custom WP_Query object is named $my_query, to use its own max_num_pages property, you just need to do the following:

printf( '<div>%s</div>', get_next_posts_link( 'Older posts', $my_query->max_num_pages ) );
printf( '<div>%s</div>', get_previous_posts_link( 'Newer posts', $my_query->max_num_pages ) );

This makes the two functions use the custom query’s values instead of the global $wp_query values which get used if nothing is passed in.

Pagination functions to avoid for clean pagination

These functions do not allow a way to filter in or pass in “max pages” overriding values, and all rely on the global $wp_query object instead.

  • posts_nav_link()
  • get_posts_nav_link()
  • get_the_posts_navigation()
  • get_the_posts_pagination()
  • the_posts_pagination()

Full Basic WP_Query example

$paged = 1;
if ( get_query_var( 'paged' ) ) {
    $paged = get_query_var( 'paged' );
} else if ( get_query_var( 'page' ) ) {
    // This will occur if on front page.
    $paged = get_query_var( 'page' );

$my_query = new WP_Query( array(
    'post_type'           => 'movie',
    'posts_per_page'      => 2,
    'paged'               => $paged,
) );
while ( $my_query->have_posts() ) : $my_query->the_post(); ?>
    <h2><?php the_title(); ?></h2>

printf( '<div>%s</div>', get_next_posts_link( 'Older posts', $my_query->max_num_pages ) );
printf( '<div>%s</div>', get_previous_posts_link( 'Newer posts', $my_query->max_num_pages ) );

What this example does is grab the paged value for the current pagination, and pass it into the WP_Query calls, along with our movie post type and 2 posts per page. Afterwards, it’ll show the title and excerpt of any found movies, and pass in its own max number of pages into our pagination functions.


While not the most flexible, it does show that there is possible pagination available for custom WP_Query calls. At least in theory, re-purposing the $wp_query variable name specifically, should make the other functions work as well, but on a personal level, I’ve never been a fan of that method. Thankfully we have a couple functions that allow overriding of the value so that we can provide a more “clean” option.

Michael is a seasoned developer who loves helping build stuff for the internet. He brings over a decade of varied experiences working with both front and back end developer stacks.

His primary focus has been WordPress and PHP and all the components that go along with them. During the day, he is a Support Engineer with WebDevStudios, helping clients get the best that they can out of their own websites.

Categories: Web DevelopmentTags: ,

Leave a Reply

Your email address will not be published. Required fields are marked *