How to Make Pagination Work with WP_Query Custom Loop in WordPress

WordPress default pagination does not work with custom loops created with WP_Query out of the box. So if you have a custom query like this –

$custom_query = new WP_Query($args);

and trying to make it work with WordPress pagination functions like previous_posts_link(), next_posts_link() and paginate_links(), it will not work.

It happens because, WordPress uses the main query $wp_query for pagination, and will ignore the custom query completely.

The solution consists of 2 parts. 

  • Fix the paged parameter – which is the current page number taken from the main query
  • Force WordPress to recognize the custom query – so that correct pagination information can be set on the main query

Fix the “paged” parameter: First we need to extract the query variable “paged” (if it is a static front page then the variable is “page”) using get_query_var(“paged”);

$paged = ( get_query_var( 'paged' ) ) ? get_query_var( 'paged' ) : 1;

// OR - for static pages with a custom template
// $paged = ( get_query_var( 'page' ) ) ? get_query_var( 'page' ) : 1;

Then pass along this variable to the array of arguments for the custom loop

$args = array(
  'posts_per_page' => get_option( 'posts_per_page' ),
  'paged' => $paged
);

$custom_query = new WP_Query( $args );

The next part involves hacking the main query a bit. Here, we assign the main $wp_query to a temporary variable, then set the $wp_query to null, then assign the custom query to $wp_query. 

$temp_query = $wp_query;
$wp_query = null;
$wp_query = $custom_query;

So now $wp_query has our custom query and also any pagination information passed by WordPress. 

Then we loop through the custom query as usual

if ( $custom_query->have_posts() ) {
  while( $custom_query->have_posts() ){ 
	$custom_query->the_post();
        ...
        ...
  }
}

wp_reset_postdata();

And add pagination links below the loop (after wp_reset_postdata() );

<div id='nxt-posts-link'>
<?php echo previous_posts_link( 'Older Posts' );?>
</div>
<div id='pre-posts-link'>
<?php echo next_posts_link( 'Newer Posts', $custom_query->max_num_pages ); ?>
</div>

Notice that next_posts_link is using $custom_query->max_num_pages instead of $wp_query->max_num_pages for correct pagination link on the “next” button.

Finally we need to reset the main query object. Else you may get unexpected results if the page has other loops running elsewhere.

$wp_query = NULL;
$wp_query = $temp_query;

Full code for a custom loop with pagination may look like as follows –

global $post; // required

// i am using a static page template hence "page", in other cases
// it will be "paged"
$paged = ( get_query_var( 'page' ) ) ? get_query_var( 'page' ) : 1;

$args = array(
  'posts_per_page' => get_option('posts_per_page'),
  'paged' => $paged
);

$custom_query = new WP_Query( $args );

$temp_query = $wp_query;
$wp_query = null;
$wp_query = $custom_query;

if ( $custom_query->have_posts() ):
  while( $custom_query->have_posts() ):
     $custom_query->the_post();
      // loop here
  endwhile;
endif;

wp_reset_postdata();

// typically these functions will be enclosed
// by 'div' elements for display / styling
previous_posts_link( 'Older Posts' );
next_posts_link( 'Newer Posts', $custom_query->max_num_pages ); 

// reset the main query object to avoid 
// unexpected results on other parts of the page   
$wp_query = NULL;
$wp_query = $temp_query;

Thats it! Post your queries in the comments below.