Different ‘posts_per_page’ setting for first, and rest of the paginated pages?

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:

/*
 * Offset posts by 10 on 'Techonology (tech)' category archive
 */
function itsme_category_offset( $query ) {
    $offset = 10;
    $ppp = get_option( 'posts_per_page' );
    $paged = $query->query_vars[ 'paged' ];

    if( $query->is_category( 'tech' ) && $query->is_main_query() ) {
        if( !is_paged() ) {

            $query->set( 'offset', $offset );

        } else {

            $paged_offset = $offset + ( ($paged - 1) * $ppp );
            $query->set( 'offset', $paged_offset );

        }
    }
}
add_action( 'pre_get_posts', 'itsme_category_offset' );

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:

function itsme_adjust_category_offset_pagination( $found_posts, $query ) {
    $offset = 10;

    if( $query->is_category( 'tech' ) && $query->is_main_query() ) {
        return( $found_posts - $offset );
    }
}
add_filter( 'found_posts', 'itsme_adjust_category_offset_pagination', 10, 2 );

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:

/*
 * Show a different no. of posts on the first page, and the rest
 * of the pages of 'Techonology (tech)' category archive
 */
function itsme_category_offset( $query ) {
    $ppp = get_option( 'posts_per_page' );
    $first_page_ppp = 5;
    $paged = $query->query_vars[ 'paged' ];

    if( $query->is_category( 'tech' ) && $query->is_main_query() ) {
        if( !is_paged() ) {

            $query->set( 'posts_per_page', $first_page_ppp );

        } else {

            // Not going to explain the simple math involved here
            $paged_offset = $first_page_ppp + ( ($paged - 2) * $ppp );
            $query->set( 'offset', $paged_offset );

            /*
             * As we are not adding a custom `$query->set( 'posts_per_page', ... );`,
             * the default `posts_per_page` setting from WP Dashboard > Settings > Reading
             * will be applied here.
             */

        }
    }
}
add_action( 'pre_get_posts', 'itsme_category_offset' );

This case is complex. So, first lets look at how the found_posts function should look like:

function itsme_adjust_category_offset_pagination( $found_posts, $query ) {
    $ppp = get_option( 'posts_per_page' );
    $first_page_ppp = 5;

    if( $query->is_category( 'tech' ) && $query->is_main_query() ) {
        if( !is_paged() ) {

            return( $found_posts );

        } else {

            return( $found_posts - ($first_page_ppp - $ppp) );

        }
    }
    return $found_posts;
}
add_filter( 'found_posts', 'itsme_adjust_category_offset_pagination', 10, 2 );

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 the posts_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 + 10
  • But correct pagination would be: 5 + 10 + 5 (because in the initial pre_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…

5 posts on 1st page + 10 posts on 2nd + 5 posts on 3rd

…thinking it’s actually doing it like this:

10 posts on 1st page + 10 posts on 2nd + 5 posts on 3rd

And since found_posts filter has no effect whatsoever on how many posts are actually displayed on a given page, our posts_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.

Leave a Comment