Properly Calculating Page Offset in Custom WordPress Queries

October 25, 2012 — 8 Comments

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.

8 responses to Properly Calculating Page Offset in Custom WordPress Queries

  1. I remember figuring this out for myself while writing my membership plugin. It was painful. Once you have it figured out it’s pretty simple and straight forward, but doing it for the first time can definitely be an arduous task.

    • Totally. Early on, pagination was probably the biggest thing that tripped me up and mainly for two reasons:

      • Next posts / Previous post are somewhat counter-intuitive in how they work
      • Crafting manual queries, then compensating for pages, offset, and the ‘page’ versus ‘paged’ query variable parameter was tough to keep straight

      I finally wrote this post just as much as a note for myself as I did to help others ;) .

  2. Michael Thomas RIchards November 1, 2012 at 4:28 pm

    This post was a lifesaver. Simple and easy to follow. Thanks Tom!

  3. Michael Thomas RIchards November 2, 2012 at 1:40 pm

    im locally developing a page right now for a client (i’m relatively new to wordpress) and am using this for their blog page. On the page itself i have 2 custom queries- one to display a featured post, and another query is basically the example you wrote above. 6 posts per page, etc etc.

    the pagination does not work on /blog or /blog/page/1 but will paginate properly on blog/page/2 (going back to the first page of posts) – is this due to the featured post query which has if(have_posts( ) && is_home( ) statement?

    • /page/1 should just rewrite to the root of the homepage or the category that you’re in – make sure that’s happening, first.

      If not, then there may be a problem with how you’re using get_query_var.

  4. Thank You,
    You Make My Day…

  5. thanks for putting this together. Other articles on WP_Query did not talk about the issue with pagination. Should probably be pointed out that if you use plugins such as QTranslate for multilingual sites, wordpress will control the language / text for the ‘Previous page’, ‘Next page’ links, so would need to omit the first arguments in the ‘next_posts_link’ and the ‘previous_posts_link’ methods, but a developer would notice this soon enough :)

Leave a Reply

*

Text formatting is available via select HTML.

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>