Whenever it comes to writing custom queries in WordPress, pagination always seems to give developers problems (myself included!).

I think this can be chalked up to the next / previous pagination links (so does next mean older, or newer?), paginating single posts as well as archive posts, and then occasionally having to write custom queries that include pagination.

One of the areas that I see most confusing – again for myself as well – is properly calculating page offsets especially when working with the WP_Query offset parameter.

The thing is, I think it can be much more simplified (or, perhaps, demystified?) when visualizing the data that you’re working with, and knowing how to use some of the existing API links.

So here’s what you need to know in order to get pagination working when working with the WP_Query offset, page, and number parameters.

A Few Assumptions

There’s several things I’m assuming while writing this article:

Nothing too complicated, right?

Visualizing Pagination

When working with WP_Query parameters specifically for pagination, there are really only three parameters that factor into the whole operation:

  • number. This is the number of posts that you want to display on the page at any given time.
  • page. The is the page that’s currently being displayed.
  • offset. This is where the query begins pulling posts. For example, if you’re on page one, then the offset will be zero; otherwise, it will be something else to “bypass” posts that’ve already been display.

Before looking at any code or trying to explain any further, here’s a way to visualize what’s going on when you’re paging through your posts:

WP_Query Offset

Visualizing Post Pagination in WordPress

In the photo above, notice that there’s a set of seven pages. This is represented by max_num_pages which will be used momentarily.

Next, only two posts will be displayed per page. Sure, it’s small but it works well enough for a demonstration.

Finally, the offset is actually represented by a formula. Basically, it’s the current page number multiplied by the current page number less one. This is what trips developers out more than anything else.

Understanding WP_Query Offset For Pagination

Offsets and Off-By-One

First, offset is nothing more than a parameter that tells WordPress where to begin pulling posts. If you’re on the first page, the offset should be zero.

If you’re on page two, then it should be however many posts you’ve already displayed (less one, since we start counting at zero) multiplied by the page that you’re on.

Make sense? Look at it this way:

  • Page 1: (1 – 1) * 1 = 0
  • Page 2: (2 – 1) * 1 = 1
  • Page 3: (3 – 1) * 1 = 2

Remember: The final value calculated for the offset is usually one less than what you’d expect because offsets start counting at zero.

Writing The Query

At this point, the only other missing piece is being able to calculate which page we’re on. Luckily, WordPress makes this available as a query string variable that can be accessed by using get_query_var.

The best way to get the current page is this:

// If the query var is set use it; otherwise, initialize it to one.
$page = get_query_var( 'paged' ) ? get_query_var( 'paged' ) : 1;

And yes, WordPress stores it in a variable called “paged.”

From there, we can begin writing the query. I’m assuming that we’re going to be doing something simple like pulling back posts ordered by descending date.

Obviously, your implementation may vary, but the pagination part should be the same:

// First, initialize how many posts to render per page
$display_count = 2;

// Next, get the current page
$page = get_query_var( 'paged' ) ? get_query_var( 'paged' ) : 1;

// After that, calculate the offset
$offset = ( $page - 1 ) * $display_count;

// Finally, we'll set the query arguments and instantiate WP_Query
$query_args = array(
  'post_type'  =>  'post',
  'orderby'    =>  'date',
  'order'      =>  'desc',
  'number'     =>  $display_count,
  'page'       =>  $page,
  'offset'     =>  $offset
);
$custom_query = new WP_Query ( $query_args );

/*
 * Use your query here. Remember that if you make a call to $custom->the_post()
 * you'll need to reset the post data after the loop by calling wp_reset_postdata().
 */

This will get you all you need in order to have a query properly paging except for one minor detail.

Defining The Pagination Links

Once you have your query parameters properly set, all that’s left to do is to define your pagination links. There are a couple of ways to do this, but I like to use next_posts_link and previous_posts_link.

The functions accept two optional parameters the second of which is a value for the maximum number of pages that the query has found. When working with custom queries, this is essential.

Here’s how I normally setup my pagination links:

<ul class="pagination">
	<li id="previous-posts">
		<?php previous_posts_link( '<< Previous Posts', $custom_query->max_num_pages ); ?>
	</li>
	<li id="next-posts">
		<?php next_posts_link( 'Next Posts >>', $custom_query->max_num_pages ); ?>
	</li>
</ul>

And that does it.

Of course, pagination is always one of those fickle things when it comes to WordPress, so if you’ve got enhancements that you’d make, or alternative ways of working with paging through data, definitely share ’em in the comments.