27

I'm working on a WordPress site and I've created a page template that displays posts by a category slug. To do this, I create a field for the page, WP_Catid, and set it equal to the category slug I want to display posts from. However, I only want five posts to show up per page with pagination links at the bottom of those posts. How do I get the pagination links to display properly?

My code is as follows:

<div id="container">
  <div id="content" role="main">
    <?php
      $btpgid=get_queried_object_id();
      $btmetanm=get_post_meta( $btpgid, 'WP_Catid','true' );
      $paged = (get_query_var('paged')) ? get_query_var('paged') : 1;

      $args = array( 'posts_per_page' => 5,
                     'category_name' => $btmetanm,
                     'paged' => $paged,
                     'post_type' => 'post' );

      $myposts = get_posts( $args );
      foreach ( $myposts as $post ) : setup_postdata( $post ); 
        echo "<div style='border:2px groove black; margin-bottom:5px;'><h3 class='btposth'>";
        the_title(); 
        echo "</h3><div class='btpostdiv'>";
        the_content();
        echo "</div></div>";
      endforeach; 
      next_posts_link( 'Older Entries'); //not displaying
      previous_posts_link('Newer Entries &raquo;'); //not displaying
      wp_reset_postdata();
    ?>
  </div><!-- #content -->
</div><!-- #container -->
Oscar Pérez
  • 4,377
  • 1
  • 17
  • 36
B_Troutman
  • 273
  • 1
  • 3
  • 4

6 Answers6

54

The sweet and short of this, don't use get_posts if you need paginated queries. get_posts works perfectly if you are going to use a custom query that doesn't need pagination, but it really becomes a big complicated mess when you need to introduce pagination.

I think the easiest and most appropriate here is to make use of WP_Query to construct your custom query, that is, if you can't use pre_get_posts to alter the main query to get your desired output from the main query.

I do think that next_posts_link() and previous_posts_link() is better to use with a custom query, ie with WP_Query. You must just remember however to set the $max_pages parameter when you make use of a custom query, otherwise your pagination will break

With a few minor tweaks, your query should look like this

<div id="container">
<div id="content" role="main">
<?php
$btpgid=get_queried_object_id();
$btmetanm=get_post_meta( $btpgid, 'WP_Catid','true' );
$paged = (get_query_var('paged')) ? get_query_var('paged') : 1;

$args = array( 'posts_per_page' => 5, 'category_name' => $btmetanm,
'paged' => $paged,'post_type' => 'post' );
    $postslist = new WP_Query( $args );

    if ( $postslist->have_posts() ) :
        while ( $postslist->have_posts() ) : $postslist->the_post(); 


             echo "<div style='border:2px groove black; margin-bottom:5px;'><h3 class='btposth'>";
                 the_title(); 
             echo "</h3><div class='btpostdiv'>";
                 the_content();
             echo "</div></div>";

         endwhile;  

             next_posts_link( 'Older Entries', $postslist->max_num_pages );
             previous_posts_link( 'Next Entries &raquo;' ); 
        wp_reset_postdata();
    endif;
    ?>

</div><!-- #content -->
</div><!-- #container -->
Pieter Goosen
  • 9,768
  • 5
  • 35
  • 55
  • Thank you. There are some PHP errors in this code (no endwhile expression, no terminating single quotation after "Older Entries", and "?>" should not be placed before the while expression). Other than that though, your code solved my problem. – B_Troutman Jul 20 '14 at 17:47
  • Doesn't get_posts use WP_Query internally? Maybe you mean that get_pages is not really good with paging? –  Apr 30 '16 at 17:21
  • 1
    @BjörnAliGöransson yes, `get_posts()` uses `WP_Query` internally, but only returns the `$posts` property from the query object. Therefor, you loose the `$max_num_pages` property which is key for paging functions. `get_posts` also legally breaks pagination by passing `no_found_rows=true` to `WP_Query` (*which makes `get_posts` faster than a normal `WP_Query` query*). As the other answers pointed out, you can page `get_posts` queries, but it does require a large amount of **unnecessary** overheads which can be simply avoided by using `WP_Query` – Pieter Goosen May 01 '16 at 05:31
  • or better yet, use [paginate_links](https://codex.wordpress.org/Function_Reference/paginate_links) – René Roth Jun 16 '16 at 19:51
  • 3
    @RenéRoth yes you can, but remember, it will still not work with `get_posts` out-of-the-box, paging functions needs to know how many pages a query will have, this is not returned by `get_posts`. The only way to page `get_posts` and know how many pages there will be is to run another `get_posts` query to return ***all*** posts in order to count them, which in turn is expensive and unnecessary. – Pieter Goosen Jun 17 '16 at 05:54
27

Pieter Goosen's answer is completely correct, and his suggestion to use WP_Query instead makes the most sense. However, I stumbled across this question whilst looking for pagination with get_posts outside of the loop, so I thought this might also be a useful alternative for anyone else:

get_posts has a direct property called offset which achieves pretty much the same thing as paged in WP_Query; but where paged refers to pagination (e.g. 1, 2, 3), offset is the actual number of posts you want to offset your query by (e.g. 5, 10, 15). With a little maths - numberToShow * pageNumber - you can get the correct offset easily:

$paged = (get_query_var('paged')) ? get_query_var('paged') : 0;

$postsPerPage = 5;
$postOffset = $paged * $postsPerPage;

$args = array(
    'posts_per_page'  => $postsPerPage,
    'category_name'   => $btmetanm,
    'offset'          => $postOffset,
    'post_type'       => 'post'
);

$myposts = get_posts($args);

The initial paged value in this example is 0 rather than 1 because, when multiplying the posts_per_page, you want the initial offset to be 0 rather than 5.

This can be most handy if you want a little more granular control rather than straightforward pagination, but should work just as well in combination with the loop in the accepted answer.

Community
  • 1
  • 1
indextwo
  • 5,535
  • 5
  • 48
  • 63
  • Your solution will works, but how will you know how many pages there would be. That is the main issue, that is why you should not use get_posts for paginated queries ;-) – Pieter Goosen Dec 09 '15 at 11:41
  • 1
    BTW, `WP_Query` uses the same exact method to calculate which posts should be returned according to page, I mean, using page number and posts per page to calculate the offset – Pieter Goosen Dec 09 '15 at 11:43
  • 1
    Last note, `get_posts` uses `WP_Query` to run its custom query. `get_posts` simply returns `WP_Query()->posts`, so why not use `WP_Query` and let is do the work – Pieter Goosen Dec 09 '15 at 11:48
  • 1
    @PieterGoosen All completely valid points; as I mentioned, your suggestion for using WP_Query makes a lot more sense in this particular situation, as clearly the full post information is required. In my specific circumstance, I was using `get_posts` to grab an array of information (and `get_posts` doesn't return _exactly_ the same as `WP_Query` - just the subset query post objects - a minor distinction - and also turns off the pagination query & ignores sticky posts). I just wanted to point out `offset` as an alternative, if not exactly perfect for the job. – indextwo Dec 09 '15 at 12:18
  • 2
    No problem, your answer is still very valid and a good alternative ;-) – Pieter Goosen Dec 09 '15 at 12:20
  • 4
    GREAT answer considering this works EXACTLY as needed for endless scrolling :) – DropHit Sep 20 '16 at 02:59
  • 1
    @DropHit That's exactly what I use it for! – indextwo Sep 20 '16 at 09:18
  • Great solution. Just one note: If you count from page 1 instead of page 0, remember to subtract 1 from your page number. `'offset' => ($page - 1) * $per_page`. – Jason Norwood-Young Jun 10 '20 at 08:58
5

Try to change your $args:

$args = array( 
'posts_per_page' => 5,
'category_name' => $btmetanm,
'post_type' => 'post',
'paged' => ( get_query_var('paged') ? get_query_var('paged') : 1 )
   );

And just after loop put this:

if (function_exists('wp_pagenavi')) {
wp_pagenavi();
}
T3KBAU5
  • 1,861
  • 20
  • 26
hamza
  • 57
  • 4
3

I will not tell you that using get_posts() is the right thing to do...but here is some basic pagination code I setup using get_posts().

EDIT: As Pieter pointed out, this isn't meant to be used in production code. But just something I was playing around with anyway to see if I could get pagination working with get_posts(). If you are in a production environment you wouldn't want to use this.

$cpt_name = 'post-type'; //add your own post type

//what pagination page are we on?
if(! empty($_GET['pag']) && is_numeric($_GET['pag']) ){
    $paged = $_GET['pag'];
}else{
    $paged = 1;
}
//you could use this if you want, just make sure to use "/page/2" instead of "?pag=2" in the pagination links.
//$paged = (get_query_var('paged')) ? get_query_var('paged') : 0;

//how many posts should we display?
$posts_per_page = (get_option('posts_per_page')) ? get_option('posts_per_page') : 10; 

//let's first get ALL of the possible posts
$args = array(
        'posts_per_page'   => -1,
        'orderby'          => 'title',
        'order'            => 'ASC',
        'fields'           => 'ids',
        'post_type'        => $cpt_name
    );

$all_posts = get_posts($args);

//how many total posts are there?
$post_count = count($all_posts);

//how many pages do we need to display all those posts?
$num_pages = ceil($post_count / $posts_per_page);

//let's make sure we don't have a page number that is higher than we have posts for
if($paged > $num_pages || $paged < 1){
    $paged = $num_pages;
}

//now we get the posts we want to display
$args = array(
        'posts_per_page'   => $posts_per_page,
        'orderby'          => 'title',
        'order'            => 'ASC',
        'post_type'        => $cpt_name,
        'paged'        => $paged
    );

$my_posts = get_posts($args);

//did we find any?
if(! empty($my_posts)){

    echo '<div id="my-posts-wrapper">';

    //THE FAKE LOOP
    foreach($my_posts as $key => $my_post){
                //do stuff with your posts
        echo '<div class="my-post">'.$my_post->post_title.'</div>';

    }

    echo '</div>';

    //we need to display some pagination if there are more total posts than the posts displayed per page
    if($post_count > $posts_per_page ){

        echo '<div class="pagination">
                <ul>';

        if($paged > 1){
            echo '<li><a class="first" href="?pag=1">&laquo;</a></li>';
        }else{
            echo '<li><span class="first">&laquo;</span></li>';
        }

        for($p = 1; $p <= $num_pages; $p++){
            if ($paged == $p) {
                echo '<li><span class="current">'.$p.'</span></li>';
            }else{
                echo '<li><a href="?pag='.$p.'">'.$p.'</a></li>';
            }
        }

        if($paged < $num_pages){
            echo '<li><a class="last" href="?pag='.$num_pages.'">&raquo;</a></li>';
        }else{
            echo '<li><span class="last">&raquo;</span></li>';
        }

        echo '</ul></div>';
    }
}

I hope someone gets some use out of this :)

EDIT: What the heck! Going to do something the wrong way...might as well do it right! Here is some LESS as well (without any mixins).

.pagination             { margin: 30px 0px;
    ul                    { display:block; list-style-type:none; margin:0 auto; padding: 0px; 
      li                  { display:inline-block; list-style-type:none; margin:0; padding:0;
        a, span           { display:inline-block; font-size: 14px; width:auto; min-width:26px; height:26px; line-height: 26px; border: 1px solid #dddddd; border-right: 0px; background:#FFFFFF; color:#FF0000; padding: 5px; text-align: center;
          &:hover         { cursor:pointer; text-decoration:none; }

          &.first         { border-top-left-radius: 3px; border-bottom-left-radius: 3px; }
          &.last          { border-top-right-radius: 3px; border-bottom-right-radius: 3px;}
        }

        span.last,
        span.first        { color: #FF0000;
            &:hover       { cursor: default; }
        }

        a.last,
        a.first           { 
            &:hover       {  }
        }

        a:hover, 
        &.active a, 
        .current          { background:#FF0000; color:#ffffff; border-color: #b21712; }

        &:last-child      { 
            a, span       { border-right: 1px solid #dddddd; }

            a             { 
                &:hover   { border-color: #FF0000; }
            }
        }
      }
    }
}
Bullyen
  • 808
  • 1
  • 8
  • 20
  • 1
    There is no query var called `pag`. Secondly, your query is extremely expensive. If you have a 1000 posts, you are going to query 1000 posts just to get the post count. You then run another query just to get the posts again. This is really bad for page speed and SEO. BTW, this is what `get_posts()` does, `$q = new WP_Query( $args ); return $q->posts;`, so without knowing it, you have used `WP_Query` – Pieter Goosen Jan 14 '16 at 05:48
  • Add `timer_start();` before your code and then `echo '

    ' . get_num_queries() . ' queries in ' . timer_stop(1, 5) . ' seconds.

    ';` after your code and check the huge difference between my code and your code, and then you tell me if you still want to use `get_posts` for paginated queries ;-)
    – Pieter Goosen Jan 14 '16 at 05:52
  • Lol, thanks Pieter. I know this is terrible and I wouldn't use it for anything personally. I just wanted to show what using get_posts would be like for pagination. Also, I am not using 'pag' as a query var. I have pag set as a GET var and then in the commented out get_query_var I am using paged. Perhaps I didn't make it clear that this was not a serious post, but just a fun attempt at getting get_posts() to work with pagination. – Bullyen Jan 14 '16 at 17:30
  • 1
    Just a tip, to reduce the load on your first query which is use to serve as post count, set `'fields' => 'ids'` in your arguments. This will only return post ids and not the complete posts. This saves tons on resources if you do not need any postdata – Pieter Goosen Jan 14 '16 at 17:37
  • Thanks for the info Pieter :) I went ahead and actually set this code up on a test site and also setup WP_query and ran some tests.There were only 9 total posts so a very small sample size, but on average the WP_query was almost twice as fast (which is probably the expected difference). – Bullyen Jan 14 '16 at 18:10
  • Yes, on small sites the difference is quite small. It really comes to a huge difference on big sites, site which have hundreds or thousands of posts ;-). To be really honest, This is a fun way to really learn and test out different ways of doing a specific task. This way you learn what works in a specific situation and which methods are better for performance. – Pieter Goosen Jan 14 '16 at 18:16
  • I think you might find [this](http://wordpress.stackexchange.com/a/167151/31545) useful as just a small self test on your local install – Pieter Goosen Jan 14 '16 at 18:22
1

WordPress Pagination Code for Posts (NEW)

Copy and paste this piece of code and enjoy that. :)

<?php
 
$currentPage = get_query_var('paged');
 
 
// General arguments
 
$posts = new WP_Query(array(
    'post_type' => 'post', // Default or custom post type
    'posts_per_page' => 10, // Max number of posts per page
    //'category_name' => 'My category', // Your category (optional)
    'paged' => $currentPage
));
 
 
// Top pagination (pagination arguments)
 
echo "<div class='page-nav-container'>" . paginate_links(array(
    'total' => $posts->max_num_pages,
    'prev_text' => __('<'),
    'next_text' => __('>')
)) . "</div>";
 
 
// Content display
 
if ($posts->have_posts()) :
    while ($posts->have_posts()) :
        $posts->the_post();
        echo "<div class='post-wrap'>";
        the_title();
        the_content();
        echo "</div>";
    endwhile;
endif;
 
 
// Bottom pagination (pagination arguments)
 
echo "<div class='page-nav-container'>" . paginate_links(array(
    'total' => $posts->max_num_pages,
    'prev_text' => __('<'),
    'next_text' => __('>')
)) . "</div>";
 
?>
Mahdi Bashirpour
  • 17,147
  • 12
  • 117
  • 144
0
//pagination to work for get_posts() in WordPress.    
//Step-1.    
 $paged = (get_query_var('paged')) ? get_query_var('paged') : 1;    
 $args = array( 'posts_per_page' => 4,         
               'order'    => 'DESC',    
               'orderby' => 'ID',    
               'paged' => $paged                                   
                    );   
                    $query = get_posts($args);  
                    foreach ($query as $key => $value) {  
                      echo get_the_title($value->ID);  
                     }  
step-2    
//Get total post count    
   $args = array( 'posts_per_page' => -1, 
                  'order'    => 'DESC',
                  'orderby' => 'ID'
                  );    
$totalPost = get_posts($args);    
$numberOftotalpost = count($totalPost);    
$maxNumberOfPage =  ceil($numberOftotalpost/4);   
    
//call pagination function    
   
echo pagination_bar($maxNumberOfPage);   

//pagination function

function pagination_bar( $maxNumberOfPage ) {

    $total_pages = $maxNumberOfPage;
    $big = 999999999; // need an unlikely integer

    if ($total_pages > 1){
        $current_page = max(1, get_query_var('paged'));

        echo paginate_links(array(
            'base' => str_replace( $big, '%#%', esc_url( get_pagenum_link( $big ) ) ),
            'format' => '?paged=%#%',
            'current' => $current_page,
            'total' => $total_pages,
                        'prev_text' => __('« Previous'),
                        'next_text' => __('Next »'),
        ));
    }
}