6

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?

This works:

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?

8
  • I think this is what you are looking for: wordpress.stackexchange.com/a/105552/21376 but "is this the right way" is a pretty broad question.
    – s_ha_dum
    Commented Nov 24, 2013 at 15:58
  • @s_ha_dum As suggested in your other answer, I've tried this: gist.github.com/anonymous/9e7716a006667a667432. It has a simpler logic and seems to work without a problem (hopefully). So does that mean the WordPress Codex doc is wrong? Don't I need to use the other function that hooks into the found_posts filter?
    – its_me
    Commented Nov 24, 2013 at 16:14
  • 1
    "Is that the right way..." and "am I doing something wrong?" are inherently bad questions for SE sites, because they solicits opinion-based answers. Please re-word to address a specific problem. Also: you're asking two separate questions (the second one being the more interesting, IMHO). Commented Nov 24, 2013 at 16:15
  • 2
    Sometimes the Codex is wrong. It is community edited. Anyone can get an account and edit it (I make changes fairly often). And I agree with @ChipBennett, "is this the right way" makes for a bad question. That is why I stated that the question is pretty broad. The question can elicit opinion based answers because there are at least a few different ways to do any one thing and people sometimes disagree about which is "best" or even about what "best" means.
    – s_ha_dum
    Commented Nov 24, 2013 at 16:22
  • @s_ha_dum Okay. 1. So, is this all I need to do then? gist.github.com/anonymous/9e7716a006667a667432. If yes, should I delete the question, or would you like to make an answer explaining that the codex is wrong and there's no need to use the found_posts filter? 2. In that case, could you please tell me how I can ask questions where I do have a working code, but am not sure if I am doing it right? That'd be very, very helpful.
    – its_me
    Commented Nov 24, 2013 at 16:25

2 Answers 2

10

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.

4
  • 2
    While this solution has worked for me – thanks! –, I had to modify the if( !is_paged() ) case in the found_posts function. When it was return( $found_posts );, WordPress apparently assumed that subsequent result pages would also use $first_page_ppp. For a total of 20 posts, the first page would show a pagination with three subsequent pages (5 + 5 + 5 + 5), instead of the correct two (5 + 10 + 5). My solution: Replacing return( $found_posts ); with: return( $first_page_ppp + ( $found_posts - $first_page_ppp ) * $first_page_ppp / $ppp );.
    – mach
    Commented Feb 25, 2015 at 11:51
  • Thank you. Still relevant after all these years. Case #2 exactly what I needed.
    – m4rlo
    Commented Dec 1, 2018 at 1:03
  • OMG thank you! been struggling with this and infinite scroll for a while... Case 1 worked for me, thanks!
    – Mike
    Commented Jan 24, 2019 at 14:23
  • THANK YOU. #2 just saved my brain after trying a dozen other methods. I have a custom WP_Query returning the first post which is "featured" and different from all the rest, then was trying to modify the main loop either by hiding the first post or offsetting in functions, both of which caused problems. #2 totally fixed everything and now I have 1 featured + 6 grid on the first page and grids of 9 on the following. THANKS!
    – fastasleep
    Commented Oct 1, 2020 at 9:10
1

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:

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

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

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.