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 responses

  1. Thanks for this! Totally helped me out 🙂

    1. Yoren Chang Avatar
      Yoren Chang

      Glad it helped! 😉

  2. Thanks. It works like Charm!!

  3. Thank you!!!

  4. 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

  5. Hello again!

    I read in: http://stackoverflow.com/questions/26425192/get-all-products-from-english-version-woocommerce-wpml

    but I dont know how to aply that to your code. How I integrate the JOINT whit the active languague in the wp_icl_translations table??

    (yes I forgot, I am using WPML as language plugin)

    1. Yoren Chang Avatar
      Yoren Chang
  6. Awesome!
    Dropped the snippet into my functions and worked like a charm just like it is.
    thank you Yoren! 🙂

    1. Yoren Chang Avatar
      Yoren Chang

      Cool! Glad I could help. It was bothering me so I’m really happy it is of use to people.

  7. 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.

    1. Yoren Chang Avatar
      Yoren Chang

      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' );
      
      
  8. 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.

    1. Yoren Chang Avatar
      Yoren Chang

      Glad I can help!

  9. 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?

    1. Yoren Chang Avatar
      Yoren Chang

      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!

  10. 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!

  11. 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’ );

  12. Hey this is auto-correcting me the code I put! That is not the code i am sending in the comment form. Here it is: http://pastebin.com/eHR15dkw

    1. Yoren Chang Avatar
      Yoren Chang

      Hey, glad you figured that out!

  13. 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?

    1. Yoren Chang Avatar
      Yoren Chang

      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?

  14. 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

    1. Yoren Chang Avatar
      Yoren Chang

      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!

      1. Hi,

        Thanks for your reply.

        Can you please tell how can i call this function in single.php?

        Thanks
        Atif

        1. Yoren Chang Avatar
          Yoren Chang

          These functions need to be in functions.php.

          1. I put the code in functions.php. how can i call the next/previous function in single template?

          2. Yoren Chang Avatar
            Yoren Chang

            Hook functions go to function.php while template functions go to template files, in this case should be single.php or so.

  15. 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?

    1. Yoren Chang Avatar
      Yoren Chang

      Hi Mark, you’ll have to update the SQL statements to make it work with category (taxonomy). This would be a bit complicated and you have to be familiar with WordPress data structure. Good luck!

    2. Hi Mark,

      Did you manage to work this out as have the same issue?

      Regards,

      James Isles

  16. Yoren,

    Great solution! I do have a question, though:

    Will this apply to ALL get_adjacent_post() calls? What if I want the prev/next links to be based on menu_order for one custom post type but use the default date-based ordering for all other post types?

    Would it be something like this?
    https://gist.github.com/anonymous/50d27caa1abf9e432bbea5c05ee7dd46

    1. Yoren Chang Avatar
      Yoren Chang

      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.

  17. 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

    1. Yoren Chang Avatar
      Yoren Chang

      Hi Derrick, yeah it definitely works with database cache since we totally use a WP native solution.

  18. Woow, thanks for share!!

  19. […] Get the right prev/next post link when order posts by menu_order […]

  20. Thank`s. Check out the share buttons. I shared on social networks (twitter, Google, facebook), but the counter shows – zero

    1. Yoren Chang Avatar
      Yoren Chang

      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…

  21. Oh my god I love you! I owe you a couple of beers. Being looking for this since days.

  22. It worked like a charm! Thank you so much!

  23. This just saved my butt. Thanks a lot, mate!

  24. Thank you so much! You’re saving me a lot of time!

  25. 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.

  26. Ali Ahsan Avatar

    Can you tell me if the products are not ordered by menu order… then how to order it alphabetically??? Thanks for your post.

  27. Kathy Avatar

    Thank you SO much! Works brilliantly!

Leave a Reply

Your email address will not be published. Required fields are marked *