After getting so close to nailing my theme I've hit the last hurdle. Essentially I have three different areas on my homepage:

  1. The latest post appears in the header and styled as a featured post
  2. Two posts below that styled as recent posts
  3. The rest of the posts (10) in date order below those

Latest post (1) and the two recent posts (2) are shown using a custom WP_Query and the remaining ten posts (3) are shown using the standard loop with an offset function which also solves the pagination BUT... there's a hiccup with page 2. For some reason, it's adding an extra three posts to the top of the list of posts (3)? After this, page 3 onwards, it's fine and works as it should. I should also mention that the latest and recent posts (1 and 2) are to be shown at the top at all times as you cycle through the pages.

Here the layout with the different areas and post numbers:


Here's the code used for the function that handles the main posts (3) and the pagination fix (the offset is used to skip the latest and recent posts shown above these):

function offset_main_query ( $query ) {
     if ( $query->is_home() && $query->is_main_query() && !$query->is_paged() ) {
         $query->set( 'offset', '3' );
add_action( 'pre_get_posts', 'offset_main_query' );

Any help would be great as this is the last bit that's tripping me up.

Thanks :)

2 Answers 2


Here's a suggestion for a general way to support different number of posts on the home page than on the other paginated pages. We should use the main query instead of sub-queries, if possible.


It seems to make sense to take the offset for paginated pages as:

offset_op = ( paged - 1 ) * pp_op + ( pp_fp - pp_op ) + offset_fp           
          = ( paged - 2 ) * pp_op + pp_fp + offset_fp 

where paged (pagination), pp_fp (posts per first page), pp_op (posts per other pages) and offset_fp (offset for the first page) are non-negative integers.

For paged=1 the offset is offset_fp, else it's offset_op for other pages.

Example #1:

First we calculate the offset for few pages to better understand this:

For paged=1:
    offset_fp = 0

For paged=2:
    offset_op = (2-2)*10 + 13 + 0
              = 13

For paged=3:
    offset_op = (3-2)*10 + 13 + 0
              = 10+13
              = 23

Here's a list of post indices on each page:

0,1,2,3,4,5,6,7,8,9,10,11,12    (offset_fp=0,  pp_fp=13, paged=1)
13,14,15,16,17,18,19,20,21,22   (offset_op=13, pp_op=10, paged=2)
23,24,25,26, 27,28,29,30,31,32  (offset_op=23, pp_op=10, paged=3)

We can see that the offset matches the indices.

Example #2:

Let's take pp_fp = 3, pp_op = 5, offset_fp=4 and calculate the offset_op:

For paged=1:
    offset_fp = 4

For paged=2:
    offset_op = (2-2)*5 + 3 + 4
              = 7

For paged=3:
    offset_op = (3-2)*5 + 3 + 4
              = 5+3+4
              = 12

and compare it to the indices:

4,5,6          (offset_fp=4,  pp_fp=3,  paged=1)
7,8,9,10,11    (offset_op=7,  pp_op=5,  paged=2)
12,13,14,15,16 (offset_op=12, pp_op=5,  paged=3)

Demo Plugin

Here's a demo implementation:

 * Plugin Name: WPSE demo
add_action( 'pre_get_posts', function( \WP_Query $query ) 
    // Nothing to do if backend or not home page or not the main query
    if ( is_admin() || ! $query->is_home() || ! $query->is_main_query() )

    // Get current pagination
    $paged = get_query_var( 'paged', 1 );

    // Modify sticky posts display
    $query->set( 'ignore_sticky_posts', true );

    // Modify post status
    $query->set( 'post_status', 'publish' );

    // Edit to your needs
    $pp_fp      = 13; // posts per first page
    $pp_op      = 10; // posts per other pages
    $offset_fp  = 0;  // offset for the first page

    // Offset for other pages than the first page
    $offset_op = ( $paged - 2 ) * $pp_op + $pp_fp + $offset_fp;

    // Modify offset
    $query->set( 'offset', $query->is_paged() ? $offset_op : $offset_fp );

    // Modify posts per page
    $query->set( 'posts_per_page', $query->is_paged() ? $pp_op : $pp_fp );  
} );

Hope you can adjust it to your needs!

  • Thanks, this is almost working! The number of posts (and the correct posts) on each page is now working but... the offset now isn't working on the homepage. Let's say for e.g. that the featured post in the header is "Post 100". The two recent posts below that would be "Post 99" and "Post 98". What's happening now is that the top post in the list below these is starting at "Post 100" where it needs to start at "Post 97". Hope that makes sense? Commented Sep 21, 2016 at 20:35
  • Good to hear you had some progress with it. If I remember correctly the idea was to combine all your three queries into a single one, as described in the first sentence of the answer. So the assumption is that the first three posts from that query belongs to parts 1) and 2). Does that makes sense? @GarryAylott
    – birgire
    Commented Sep 21, 2016 at 21:53
  • I'm not sure I follow you @birgire If I have three sets of posts at different places within the page (header, recent posts section then regular posts in the main section below those) I'm not sure how I can combine them into one query as I need to separate them into different sections/divs in the page? Commented Sep 22, 2016 at 6:26
  • Here's a quick layout with the different areas and post numbers which hopefully explains better what I'm looking to do (better than I can explain anyway!)... @birgire link Commented Sep 22, 2016 at 7:08
  • 1
    Great to hear you could modify this to your needs. I quickly adjusted the answer to hopefully make it easier for adjustments @GarryAylott
    – birgire
    Commented Sep 23, 2016 at 18:35

I would probably try without the && !$query->is_paged() (you already have 2 conditions that should be enough, even just the first one should work actually) and if that doesn't work, try to output the $paged value and see how you can tweak the function better.

Some debug ideas:

// get current page we are on. If not set we can assume we are on page 1.
$paged = (get_query_var('paged')) ? get_query_var('paged') : 1;
// are we on page one?
if(1 == $paged) {

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.