How to Use the “offset” Parameter Without Breaking Pagination

This article explains how to correctly use the offset parameter in queries.

It is assumed that you are already familiar with how to use WordPress filters and have a good understanding of working with the wpdb class.

A common scenario where using the offset parameter leads to incorrect pagination is when WP uses the offset to build pagination, and improper usage will affect the output. To work around this issue, you need to manually integrate into the pagination: determine if the loop has additional pagination pages and dynamically define the offset parameter for the current page using the pre_get_posts filter. Additionally, you need to adjust the pagination calculation by WP to exclude posts that we want to "skip" using the offset parameter. This can be done using the found_posts filter, which helps us "subtract" the necessary number of posts.

The Essence of the Problem

The offset is useful as it allows developers to skip a specified number of posts before starting to display them. However, many novice developers encounter a problem when using the offset parameter in a query, breaking the site's pagination.

This behavior is logical when you understand what is happening. The offset argument is used by WP when building pagination, and when developers set it manually, pagination stops working because this argument is overwritten, and WP dynamically changes it on each pagination page.

Problem Solution

To use the offset parameter in WP queries and ensure that pagination works correctly, you need to dynamically specify the offset for each page, taking into account the initial "shift" of posts. You also need to recalculate the number of pages in the pagination. This can be done using 2 hooks:

pre_get_posts - allows us to intervene in the query before it is executed. Here, we need to ensure that our offset is applied only on the first page.

found_posts - allows us to adjust the query result count. Here, we need to ensure that our offset is applied when counting on all other pages except the first one.

Offset and Pagination Adjustment

First step.

Using the pre_get_posts hook. Since this hook is an action, not a filter (a filter implies processing and returning processed data), we need to pass the $query object to our custom function by reference, allowing us to directly modify the object inside the class without returning any data.

To ensure that we are modifying the desired query, we need a small preliminary check at the beginning $query->is_posts_page. In this example, we want to skip the first 10 posts on the site's homepage:

add_action('pre_get_posts', 'myprefix_query_offset', 1 );
function myprefix_query_offset(&$query) {
	// check if this is the desired query...
	if ( ! $query->is_posts_page ) {
		return;
	}

	// define the required offset...
	$offset = 10;

	// determine how many posts per page we will display (get data from settings)
	$ppp = get_option('posts_per_page');

	// if this is a pagination page...
	if ( $query->is_paged ) {

		// calculate the offset on the pagination page (offset + current page (minus one) x posts per page)
		$page_offset = $offset + ( ($query->query_vars['paged']-1) * $ppp );

		// apply the calculated offset
		$query->set('offset', $page_offset );

	}
	else // if this is not a pagination page (first page)
	{

		// use only the offset...
		$query->set('offset',$offset);

	}
}

Second step.

WP will not consider our offset when counting the number of posts in the query. Now, the posts will be displayed correctly, but when WP builds the pagination numbers, it will assume that there are 10 more posts and add an extra pagination page, assuming that 10 posts are displayed per page. The last pagination page will exist, but when clicking the link, our modified query will return an empty result. Oops!

This small pagination problem is solved using the found_posts filter:

add_filter('found_posts', 'myprefix_adjust_offset_pagination', 1, 2 );
function myprefix_adjust_offset_pagination($found_posts, $query) {

	// again, define our offset...
	$offset = 10;

	// again, make sure we are editing the desired query (on the homepage)...
	if ( $query->is_posts_page ) {
		// change the number of found posts, reduce by our offset (10)...
		return $found_posts - $offset;
	}
}

Now, everything is ready! Pagination and the required offset should work correctly.