Get the right prev/next post link when order posts by menu_order

There are 2 functions in WordPress to let you display previous and next post links: previous_post_link and next_post_link. In general, they work so well and really save a bunch time when coding a theme.

Recently in one of my projects, I created several custom post types, and made the posts ordered by menu_order. In such scenario, I found the previous and next post links didn’t behave as I expected, they still return the links based on the order of post ids.

Before I started to write my own version of previous_post_link, luckily I found the filters in get_adjacent_post could order the posts as I want. Here’s the snippet I use in my project:

At first I just added filters to change the sort, but it didn’t work. Then I realised the get_{$adjacent}_post_where should be filtered either, to help the SQL get the right posts for us to sort.

If you are interested in how to make the prev/next post links ordered by meta value, please read this thread on StackExchange for more information.

46 thoughts on “Get the right prev/next post link when order posts by menu_order”

  1. Thanks Yoren!
    I add your code in the function.php of my child theme, Its work! now is ordering by menu order but does not respect the languages (I am setting a multilanguage site), when I click next or prev load the rigth woocommerce product but from another language.

    Any Ideas?

    Thanks in advance

    Reply
  2. So if I understand this right, after adding these filters you would just need to use the native wordpress single post navigation, right?

    How would you exclude password protected posts? Couldn’t come up with a working solution yet.

    Reply
    • Jonas, have you tried to exclude posts with password with SQL statement like this:

      
      function my_previous_post_where() {
      	global $post, $wpdb;
      	return $wpdb->prepare( "WHERE p.menu_order < %s AND p.post_type = %s AND p.post_status = 'publish' AND p.post_password = ''", $post->menu_order, $post->post_type);
      }
      add_filter( 'get_previous_post_where', 'my_previous_post_where' );
      
      
      Reply
  3. This is brilliant Yoren, thank you. I researched for a while but had no idea how to correctly write the DB request. I am learning a lot with your post. Thanks a lot.

    Reply
  4. Hey Yoren, can I ask you something else?

    I hope it doesn’t feel like I am abusing your kindness. I definitely have a feeling that you know much more than I do and maybe you can give an insight into this.

    I am using Polylang plugin, which assigns each post to a taxonomy ‘language’. While using get_adjacent_post() I want to bring only those posts that belong to the language currently displayed.

    So I am writing the following:

    $prev_post = get_adjacent_post( true, ”, true, ‘language’ );
    $next_post = get_adjacent_post( true, ”, false, ‘language’ );

    But this brings the next and previous posts from all the languages, independently from the language I am currently in.

    Should I just add this filter in ‘get_previous_post_where’ as you specified in this article and specify the ‘language’ taxonomy in there? How exactly would you filter the taxonomy in the $wpdb->prepare?

    And lastly, if you don’t mind explaining, how on earth did you learn to write the exact filtering for the $wpdb? I am reading https://codex.wordpress.org/Class_Reference/wpdb but it’s not exactly clear explaining how to do what. For example if I add “AND p.taxonomy = ‘language'” following your examples above, it says: [Unknown column ‘p.taxonomy’ in ‘where clause’]. How did you learn it?

    Reply
    • Hi Jonas,

      If we take a look at the get_{$adjacent}_post_where filter (http://adambrown.info/p/wp_hooks/hook/get_%7B$adjacent%7D_post_where?version=4.1&file=wp-includes/link-template.php), we can see that there’s a very important variable that I removed from my gist, which is a $where at the end of the SQL statement.

      I can’t recall why exactly I removed it, it seemed break my query at the time of my writing. But now if put it back can fix your problem, I’d love to update my gists accordingly.

      So can you try this filter function again:

      function my_previous_post_where() {
      	global $post, $wpdb;
      	return $wpdb->prepare( "WHERE p.menu_order < %s AND p.post_type = %s AND p.post_status = 'publish' AND p.post_password = '' $where", $post->menu_order, $post->post_type);
      }
      add_filter( 'get_previous_post_where', 'my_previous_post_where' );

      And let me know the results?

      BTW, I suppose in this case, the $wpdb is not the key to solve the issue, but how exactly the get_{$adjacent}_post_where works. I spent a lot of time to read the code and luckily managed to understand it.

      Let’s see if the function above can work this time, and I’d love to explain much more clearly if you still need my help. Thanks!

      Reply
  5. I made it work 😀

    With your help and pointing me to the source files, I took a look at the wp-includes/link-template.php and figured in line 1514 how they were calling these taxonomies.

    So this code so far seems to do the trick:

    function my_previous_post_where() {
    global $post, $wpdb;
    return $wpdb->prepare( “WHERE p.menu_order menu_order, $post->post_type);
    }
    add_filter( ‘get_previous_post_where’, ‘my_previous_post_where’ );

    I use it together with $prev_post = get_adjacent_post( true, ”, true, ‘taxonomy-name’ ); in my theme files.

    Need to test it intensively. Thanks a lot for your help and time!

    Reply
  6. Sorry, in my excitement I pasted the wrong code 🙂
    Here is the functional one (note that in the code you sent me above the $where was undefined, that’s why it wasn’t working)

    Working snippet:

    function my_previous_post_where() {
    global $post, $wpdb;
    return $wpdb->prepare( “WHERE p.menu_order menu_order, $post->post_type);
    }

    add_filter( ‘get_previous_post_where’, ‘my_previous_post_where’ );

    Reply
  7. Thanks for the code, working great – as long as the posts are on the same level (hierarchically). I’m not seeing links to if the previous or next post would be on a parent level.
    Is it possible to include those?

    Reply
    • Thomas, from the SQL statements you can see we actually don’t specify if the prev/next post should be in the same level. So can you make sure if the “menu_order” do in the right sequence?

      Reply
  8. Hi,

    I am a newbie in WordPress.. I am trying to use this code i put all the code in functions.php how can i call in single.php?

    I have created a custom filed called sorting_order, the sorting is working fine on main projects page but when you click on single page the next/previous post not showing by same sorting order.

    This code seems like sorting next/previous post.

    Please can you help me out.

    Thanks in Advance

    Thanks
    Atif

    Reply
    • hello, You just need to update the sort SQL statements to integrate the extra field you’ve added in the *.postmeta table. You don’t have to be familiar with WP, if you’re good enough at MySQL, you will figure that out!

      Good luck!

      Reply
  9. Hi!

    Thanks for this great code. I have one question though. When I’m using this code the next and prev links work for all the posts of the custom post type. What I have to include in this code to ensure that the next and prev links will be target only posts that belong to the same category?

    Reply
    • Hi Ben, yeah this filter will be applied to every get_adjacent_post() call. So your method looks correct to me, though I haven’t really tested it.

      Reply
  10. Hello Yoren,

    Great solution! It worked like a charm, thanks for taking the time to putting this up. It came in handy for a project – portfolio I was working on.

    I’m wondering this should work with database cache settings on a plugin right?

    Thanks

    Reply
    • Hey thanks! Not sure if the counter has been broken for a while. It’s a free gadget so there’s always some issues here and there…

      Reply
  11. I’ve been searching for a way to do this for ages and your code works for me, BUT it’s working in reverse for some reason. On my first post it shows a PREV button and continues in the right order but in completely the wrong direction. I can’t figure out why.

    Reply

Leave a Comment