How do you set the posts_per_page
query setting so that a different number of posts are displayed on the first of the paginated pages (of home, archive, search, etc.) and the rest of them?
For example, say I’d like to display 10 posts on the first of the paginated pages of category archives, and 15 on the rest of them. How do I do it?
function itsme_category_offset( $query ) {
if( $query->is_category() && $query->is_main_query() ) {
if( !$query->is_paged() ) {
$query->set( 'posts_per_page', 10 );
} else {
$query->set( 'offset', 10 );
$query->set( 'posts_per_page', 15 );
}
}
}
add_action( 'pre_get_posts', 'itsme_category_offset' );
But…
According to the Codex entry for WP_Query()
pagination parameters:
offset (int) – number of post to displace or pass over. Warning: Setting the offset parameter overrides/ignores the paged parameter and breaks pagination
And according to the linked Codex entry for a workaround:
Specifying hard-coded offsets in queries can and will break pagination since offset is used by WordPress internally to calculate and handle pagination.
The indicated workaround uses a function that hooks into found_posts
filter and establishes the offset. It supposes that I should do something like this:
function itsme_category_offset( $query ) {
if( $query->is_category() && $query->is_main_query() ) {
$paged = get_query_var( 'paged' );
if( 0 == $paged ) {
$query->set( 'posts_per_page', 10 );
} else {
$offset = 10 + ( ($paged - 2) * 15 );
$query->set( 'offset', $offset );
$query->set( 'posts_per_page', 15 );
}
}
}
add_action( 'pre_get_posts', 'itsme_category_offset' );
function itsme_adjust_category_offset_pagination( $found_posts, $query ) {
$paged = get_query_var( 'paged' );
if( $query->is_category() && $query->is_main_query() ) {
if( 0 == $paged ) {
$offset = 0;
} else {
$offset = 10 + ( ($paged - 2) * 15 );
}
return $found_posts - $offset;
}
}
add_filter( 'found_posts', 'itsme_adjust_category_offset_pagination' );
Since my simpler function works already, is the Codex warning still correct? (i.e. should I do it as shown in the second code block?) Was this offset/pagination issue fixed in a recent version of WordPress? And if so: how?
Case #1: Simple Offset
You want to ‘offset’ posts of a category archive by ‘n’, i.e. you simply don’t want to show the first/latest ‘n’ posts in an archive.
That is, (considering the
posts_per_page
setting in WP Dashboard > Settings > Reading is set to 10) you want posts 11 to 20 to be shown on the first page (e.g.example.com/category/tech/
), 21 to 30 on the second (e.g.example.com/category/tech/page/2/
), 31 to 40 on the third, and so on.Anyway, here’s how you’d do that:
Then, there’s one more thing. Before creating pagination, WordPress looks at the total number of posts that the
WP_Query
class reports finding when it runs a query.So, for the pagination to work properly, you need to remove the
offset
from the total number of posts that the query finds (because the first 10 posts are not being shown). And this is how you’d do it:That’s it!
PS: (A more technical explanation can be found here.)
Case #2: Conditional Offset
As it is in my case, you want to show ‘m’ number of posts on the first page of an archive, and ‘n’ posts on the others.
That is, (considering you want to show only 5 posts on the first page of the archive, and have the rest of the pages adhere to the
posts_per_page
setting in WP Dashboard > Settings > Reading, which is set to 10) you want posts 1 to 5 to be shown on the first page (e.g.example.com/category/tech/
), 6 to 15 on the second (e.g.example.com/category/tech/page/2/
), 16 to 25 on the third, and so on.Here’s how you’d do that:
This case is complex. So, first lets look at how the
found_posts
function should look like:Unlike in Case #1, here we are not eliminating any posts from the total; we need to show all of them; except we want to show a set no. of posts on the first page of the category archive, and a different no. of posts on the rest of the paginated pages.
The problem is, in order to calculate the no. of pages for generating pagination,
$query
(WP_Query) looks at theposts_per_page
setting for the current page. Since we set it as ’10’ for all other pages except the first, when on any other page except the first,$query
assumes it’s the same for all pages (including the previous/next ones i.e. including the first page) and starts calculating pagination based on that.So for example, on 2nd page (if we take total no. of posts as ’20’), according to:
$query
pagination should be: 10 + 10pre_get_posts
function we set pagination for the first page as ‘5’)So, when on 2nd page (or any page except the first), as per our example, we need to make
$query
believe that the total no. of posts is 25 so that it generates a correct pagination like this……thinking it’s actually doing it like this:
And since
found_posts
filter has no effect whatsoever on how many posts are actually displayed on a given page, ourposts_per_page
settings from the first function will prevail, with no pagination issues.That should clear things up. (And do let me know if anything’s off.)
So, to answer the question directly, yes, you pretty much always need to follow up with a function that hooks into
found_posts
filter hook, to adjust the$query
according to your custom rules, and make sure the pagination doesn’t get messed up.I don’t have enough stackexchange points to comment on the above, but I do have a correction.
Case #1: Simple Offset worked for my site very well. It just needs a fix so that it returns the default offset when the special condition isn’t met: