Improving WordPress page navigation: fixing next_post_link() and previous_post_link()

WordPress’ next_post_link() and previous_post_link() functions are used to navigate between posts in single post (permalink) view. They work great…until you come to the very first or the very last post.

When viewing the latest post, next_post_link() displays nothing; when viewing the earliest post, previous_post_link() displays nothing. This can result in some less than friendly navigation.

In this post, I’ll show you how to use a built-in WordPress function and PHP conditionals to improve your WordPress page navigation.

The problem

Here is an example of next_post_link() and previous_post_link() at work on our blog’s old theme:

wp_post_functions_example

When viewing a post that is neither the newest nor the oldest, the post navigation links display as expected.

The trouble with these functions comes when you get to the first or last post. In the case of the first, you’ll have something like this:

wp_post_functions_missing_previous

When viewing the oldest post, the "previous post" link disappears, leaving an ugly hole.

And when viewing the last post, you’ll have something like this:

wp_post_functions_missing_next

Similarly, when viewing the newest post, the "next post" link disappears.

To the rescue: get_adjacent_post

We’re going to use the obscure WordPress function get_adjacent_post() to check if… (shockingly) …there is an adjacent post. If there isn’t, we output whatever HTML markup we like to take the place of the gaping hole we’re trying to fill. If there is an adjacent post, we output it as normal.

The function retrieves a link to the adjacent post (either next or previous) and takes the following form…

get_adjacent_post([bool $in_same_cat, string $excluded_categories, bool $previous])

…and has these parameters:

  • $in_same_cat : Whether the link should be in the same category
  • $excluded_categories : A comma-separated list of excluded category IDs
  • $previous : Whether to retrieve the previous post

The solution, with sample markup

The code block below contains the markup you should add to your single.php template file. Place it where you’d like your navigation to appear, then adjust the parameters and output to suit your needs.

<span id="prev">
   <?php previous_post_link('%link', '&laquo; Previous'); ?>
   <?php if(!get_adjacent_post(false, '', true)) { echo '<span>&laquo;Previous</span>'; } // if there are no older articles ?>
</span>
<span id="next">
   <?php next_post_link('%link', 'Next &raquo;'); ?>
   <?php if(!get_adjacent_post(false, '', false)) { echo '<span>Next &raquo;</span>'; } // if there are no newer articles ?>
</span>

I then added this line to my CSS file:

#prev span, #next span { color:#ccc; } /* lighter than the normal anchor text */

And here is the final result:

wp_post_functions_result

The final result, showing user-friendly language at both ends.

In the example above, I grayed out the inactive links. If you have more room, you might get creative by replacing the text with something like “Sorry, there aren’t any newer posts!”

Ryan is the senior web developer at 3 Roads Media. He has been working with HTML since 1996.

34 Comments

  1. JimmyBean October 1, 2009

    I don’t know If I said it already but …Excellent site, keep up the good work. I read a lot of blogs on a daily basis and for the most part, people lack substance but, I just wanted to make a quick comment to say I’m glad I found your blog. Thanks, :)

    A definite great read..Jim Bean

  2. Ryan Burney October 2, 2009

    Thanks for the comment Jim!

  3. chibi October 19, 2009

    Hi, thanks for this tutorial. I was wondering if you could tell me how to change the navigation system of a wordpress blog? Right now, on my home page- there’s a link which says “Previous Comic”, which leads to “/page/2″. Is it possible to make that link to a permalink of the previous post (comic), instead of page 2?

    I’ve been browsing the net and can’t seem to find out how to do this..

    Thanks for your time!

  4. Ryan Burney October 19, 2009

    Hi Chibi,

    The WordPress tags used in this tutorial only work on single post (permalink) pages, i.e. those pages that only show one post at a time.

    As such, there’s no way to do what you’re asking from the homepage. WordPress has a couple of functions that let you skip to older posts and newer posts; you can read about those here and here.

  5. chibi October 19, 2009

    Ah..really? It seems possible in wordpress hosted webcomics, e.g http://www.wetherobots.com/ though. Any ideas?

  6. Ryan Burney October 19, 2009

    Chibi,

    After doing a bit of searching I found a solution that should do the trick. Place the following bit of code in The Loop where you want the link to appear:

    $wp_query->is_single = true;
    previous_post_link();

    This will tell WP to treat the current page as a single page, even if it’s your home page. I hope that helps.

  7. chibi October 20, 2009

    Thanks so much for taking the time to reply :)

    I tried putting that code in and nothing seems to happen. I’m probably putting it in the wrong place.. it just seems to display the text.

    Can you be any more specific?

    Thanks!

  8. Ryan Burney October 20, 2009

    No problem ;)

    I tested this in my index.php file and it works for me:

    < ?php
       query_posts('showposts=1');
    
       if(have_posts()) : while(have_posts()) : the_post();
    
          $wp_query->is_single = true;
          previous_post_link();
    
          // display your most recent post here
    
       endwhile; endif;
    ?>

    The query_posts bit will show only your most recent post on the homepage, which is what I assume you’d like to do. Then you step into The Loop, and the next bit of code tells WP to treat your index.php like a single post page, so the previous_post_link function works like you want it to. Place this wherever you’d like the link to your previous comic to appear.

    Below that, you can use WP functions like the_title and the_content to display your most recent post however you like.

    Then we exit The Loop.

    Let me know if you have trouble and I can take a closer look at what you’re doing.

  9. chibi October 20, 2009

    Great! It works!
    Thank you so much for helping me out with this. You’re a real life saver!

  10. Ryan Burney October 20, 2009

    Great – glad I could help!

  11. fatihturan November 30, 2009

    This method nice but it doesn’t working for me. This is my custom page template for showing post with images.

    When i use get_adjacent_post() function then everytime showing disabled prev/next elements (can you look this video?).

    So what i must do for use your method?

  12. Ryan Burney November 30, 2009

    Hi Fatihturan,

    It looks like there are just a couple of small differences between the code I provided above and the code in your custom page template. I’m looking at lines 44-47 of your template.

    You’re using the functions previous_posts_link and next_posts_link (with an “s”), but you should be using previous_post_link and next_post_link (without the “s”).

    Also, on line 47 of your template, make sure it reads like this:

    < ?php if(!get_adjacent_post(false, '', false)) { echo '< a href="javascript:;" rel="nofollow">Sonraki »'; } ?>
    

    You have (false, ”, true) instead of (false, ”, false).

  13. fatihturan November 30, 2009

    But if i use previous_post_link (without s) it’s doesn’t showing next/prev links.

    I think previous_post_link (without s) functions is using on single.php template.

    If you look my template you will see what i’m using (custom page template).

  14. Ryan Burney December 4, 2009

    Ahhh, I hadn’t noticed that you were using a custom template.

    The reason the prev/next links are disabled is because there are no other posts to show; you’ve limited your query to 2 posts, so WordPress is only going to pull those 2 posts and show them; there are no other posts to link to.

    Why not use single.php? You’ll have to reinvent the wheel if all you’re trying to do is get a custom template to function like single post view.

    If you have other posts that need to use single.php, and you also want to use single view to show some photos, then you can add some conditionals at the top of your single.php to check what page you’re on, e.g.:

    < ?php if(is_page('photos')) : ?>
       // do your thing
    < ?php else : ?>
       // standard single post view
    < ?php endif; ?>
    
  15. JP December 11, 2009

    I was going to use the empty() function to check if next_post_link and previous_post_link returned anything, then act accordingly, but your use of get_adjacent_post is more elegant. Thanks for sharing.

    –JP

  16. Missingtoof January 30, 2010

    Thanks! That get_adjacent_post function is exactly what I was looking for. Never heard of it before, but it did the trick. Put it in some nested if statements and got the empty nav spans conditionally removed from all my pages.

    Not sure how to post code or I’d share.

  17. Si Davies February 6, 2010

    Excellent article, I’ve been looking for a solution for this for too long! Thanks for posting.

  18. venkat April 6, 2010

    Hi Ryan,

    I’ve been looking for this solution.. and thanks for making it look easier. Can it be possible to add an hover effect to those next/previous links.. so, they reveal the post title when hovered??

    thanks again/

  19. Ryan April 7, 2010

    Hey Venkat,

    I’m glad you enjoyed the tutorial! If I understand you correctly, you’d like the links to say “Previous” and “Next” normally, but on hover display the title of the previous or next post. If that’s the case, you may be able to do this with straight CSS and some tweaks to your WordPress template.

    Try this:
    1. In your WP template, store the title of the blog post in a PHP variable (for example, $title = get_the_title('...');)
    2. Echo the title of your previous/next post into a < div> or other container, and hide it with some CSS
    3. On hover of the “Previous/Next” links, use CSS to hide the “Previous/Next” and then show the post titles.

  20. venkat April 7, 2010

    Man you are awesome.. thanks a load!

  21. venkat April 8, 2010

    Hi ryan.. i’ve tried to use get_the_title() variable… but.. couldn’t figure it well..

    Mind a code snippet when possible.. please!

  22. Ryan April 8, 2010

    Hey Venkat,

    Turns out you don’t need to use get_the_title(). Sorry :)

    Replace the code from the tutorial with this (be sure to remove the spaces!):

    < span id="prev">
       < div>< ?php previous_post_link('%link'); ?>
       < div>< ?php previous_post_link('%link', __('« Previous', 'elegante')); ?>
       < ?php if(!get_adjacent_post(false, '', true)) { _e('« Previous', 'elegante'); } ?>
    < /span>
    < span id="next">
       < div>< ?php next_post_link('%link'); ?>
       < div>< ?php next_post_link('%link', __('Next »', 'elegante')); ?>
       < ?php if(!get_adjacent_post(false, '', false)) { _e('Next »', 'elegante'); } ?>
    < /span>
    

    Then, in your theme’s CSS file, add the following rules:

    #prev div, #next div { display:none; }
    #prev:hover div, #next:hover div { display:inline; }
    #prev div + div, #next div + div { display:inline; }
    #prev:hover div + div, #next:hover div + div { display:none; }
    

    I’ve only tested in Firefox, so let me know if you have any trouble!

  23. venkat April 9, 2010

    thats cool ryan.

    Thanks for ya help. It works :)

  24. Jez Thompson June 18, 2010

    Hello.

    Where and how do i add the $in_same_cat into your simple example?

    Cannot seem to get it to work..

    Cheers :)

  25. Ryan June 18, 2010

    Hey there Jez,

    Take a look above, below the heading that says “The solution, with sample markup.” The get_adjacent_posts() function takes the $in_same_cat as the first parameter. Set it to “true” if you only want to retrieve posts that are in the same category as the one you’re viewing; “false” if you want to retrieve posts in any category.

    What exactly is not working for you? Maybe I can be of more help if you provide some more details.

  26. Jez Thompson June 18, 2010

    Cheers Ryan, so it looks like this…

    <?php if(!get_adjacent_post(true, '', true)) { echo '«Previous'; } // if there are no older articles ?>
    
    <?php if(!get_adjacent_post(true, '', false)) { echo 'Next »'; } // if there are no newer articles ?>
    

    Because i tried that and it just scrolls through all the posts, i am using this in a news category and want it to work just with say cat id 9..

  27. Ryan July 6, 2010

    Hey Jez,

    I was having trouble with email notifications so I apologize for the delayed response. If you take a look at the functions above you’ll see that they take a comma separated list of excluded categories as the second parameter. So to exclude categories 4 and 6 you would put:

    < ?php if(!get_adjacent_post(false, '4,6', true)) { echo '«Previous'; } ?>
  28. 2k July 6, 2010

    Hi there, thanx for great solution. But can you explain how to add in { echo ‘Next »’; } instead of word: Next.?

  29. Ryan July 6, 2010

    Hi 2K, I’m not sure what your question is; would you mind clarifying?

  30. Matt July 29, 2010

    How would you make the “previous” or “next” be an image button instead of text?

  31. Ryan July 29, 2010

    Hey Matt,

    All you need to do is replace this piece:

    echo '< span>«Previous< /span>';

    with something like:

    echo '< img src="xyz.png" alt="Previous" />';

    You can even replace the img HTML code with a variable:

    $image = '< img src="xyz.png" alt="Previous" />';
    
    echo $image;
    
  32. 2k July 30, 2010

    Ryan, when using image like this, instead of text, is it possible to get alt=”previous post title name” instead alt=”previous”?

  33. Ryan August 2, 2010

    Hey 2k,

    I’m not really sure — you might be able to use get_the_title(), but I don’t know which post ID you’d pass in.

  34. tim August 18, 2010

    thanks for the tip. just what is was looking for :)

Post a Comment

  • *
  • *

3 Roads Media v 7.0   All material © 2010