I created two custom post types that share the same category taxonomy. I'm using next_post_link() and previous_post_link() to navigate through posts with the same category but it only works for individual post types.

I found the following code that links post types but there is no option for next and previous post to be within the same taxonomy/category as the current post.

 * Replacement for get_adjacent_post()
 * This supports only the custom post types you identify and does not
 * look at categories anymore. This allows you to go from one custom post type
 * to another which was not possible with the default get_adjacent_post().
 * Orig: wp-includes/link-template.php 
 * @param string $direction: Can be either 'prev' or 'next'
 * @param multi $post_types: Can be a string or an array of strings
function mod_get_adjacent_post($direction = 'prev', $post_types = 'post') {
    global $post, $wpdb;

    if(empty($post)) return NULL;
    if(!$post_types) return NULL;

        $txt = '';
        for($i = 0; $i <= count($post_types) - 1; $i++){
            $txt .= "'".$post_types[$i]."'";
            if($i != count($post_types) - 1) $txt .= ', ';
        $post_types = $txt;

    $current_post_date = $post->post_date;

    $join = '';
    $in_same_cat = FALSE;
    $excluded_categories = '';
    $adjacent = $direction == 'prev' ? 'previous' : 'next';
    $op = $direction == 'prev' ? '<' : '>';
    $order = $direction == 'prev' ? 'DESC' : 'ASC';

    $join  = apply_filters( "get_{$adjacent}_post_join", $join, $in_same_cat, $excluded_categories );
    $where = apply_filters( "get_{$adjacent}_post_where", $wpdb->prepare("WHERE p.post_date $op %s AND p.post_type IN({$post_types}) AND p.post_status = 'publish'", $current_post_date), $in_same_cat, $excluded_categories );
    $sort  = apply_filters( "get_{$adjacent}_post_sort", "ORDER BY p.post_date $order LIMIT 1" );

    $query = "SELECT p.* FROM $wpdb->posts AS p $join $where $sort";
    $query_key = 'adjacent_post_' . md5($query);
    $result = wp_cache_get($query_key, 'counts');
    if ( false !== $result )
        return $result;

    $result = $wpdb->get_row("SELECT p.* FROM $wpdb->posts AS p $join $where $sort");
    if ( null === $result )
        $result = '';

    wp_cache_set($query_key, $result, 'counts');
    return $result;


$prev = mod_get_adjacent_post('prev', array('post', 'custom1', 'custom2'));
$next = mod_get_adjacent_post('next', array('post', 'custom1', 'custom2'));

<?php if($prev) : ?>
    <a href="<?php echo get_permalink($prev->ID)?>">&laquo; Go back in time</a>
<?php endif; ?>

<?php if($next) : ?>
    <a href="<?php echo get_permalink($next->ID)?>">Next: <?php echo $next->post_title; ?> &raquo;</a>
<?php endif; ?>

How can I navigate the posts with the same taxonomy?

  • A post, does it belongs to only one term. If not, how are you determining which term to use – Pieter Goosen Nov 30 '14 at 16:59
  • The post belongs to 1 or more categories. I'm determining through what categories to cycle based on the current post categories. – at least three characters Nov 30 '14 at 19:06

Here is my take on things. I have scrapped your complete idea and went with an alternative solution which doesn't use SQL. I have done some testing and found my method to be much faster that the code in your question


You first need to get the current post id, which I get through get_queried_object_id(). The post ID will be used to retrieve:

  • The post terms the post belongs to with wp_get_post_terms(). To speed things up, only the ID's of the terms will be returned. The first ID will be used (you can modify the code here to decide which term will be used if a post have more than one term) and this will be used to retrieve all the posts which has this certain term

  • The post ID's of the posts that is directly adjacent to this post to determine and retrieve the next and previous post from this one

All the info above will be used in a tax_query with get_posts to retrieve all the posts that shares the term from the current post. In the function, the default taxonomy is category and the post_type is set to any to get all the posts that has this specific term

Again, to make the code faster and to safe on resources, we only going to get the post ID's as this is all that is needed

Now comes the important parts of the code. We now need to determine the following:

  • The current position of the current post in the returned array of post ID's from the custom get_posts query. The function used here is array_search

  • If there is a post before or after this post (next or previous posts, the definitions are the same as for the build in functions next_post_link() and previous_post_link()), get the ID's of these posts

  • Use the ID's with get_post to retrieve the next and previous post's titles from the current post

Lastly will be to return the links. I have set messages if the current post is either the first or last post in the array and there are no next or previous post. You can decide what you want to do here, and for all that matters, the rest of the code

To make the code even faster and more efficient, I have made use of the Transient API which you can read further on. I have also used the transition_post_status action hook to hook a function to delete these transients whenever the post status of a post change. This includes new posts being published, post being updated and post deleted/undeleted


Here is the code. This goes into your functions.php

function get_post_link( $taxonomy = 'category', $post_type = [ 'any' ] ) {

    $id             = get_queried_object_id(); // Get the current post ID
    $transient_id   = 'post_number_' . md5( $id . $taxonomy . implode( ',', $post_type ) ); //Create a unique transient id

    if ( false === ( $links = get_transient( $transient_id ) ) ) {

        // Get the terms a post belongs to
        $terms = wp_get_post_terms( $id, $taxonomy, array( 'fields' => 'ids' ) ); 

        // Use a tax_query to get all posts from the given term
        // Just retrieve the ids to speed up the query
        $post_args = [ 
            'post_type'         => $post_type,
            'fields'            => 'ids',
            'posts_per_page'    => -1,
            'tax_query'         => [
                    'taxonomy'          => $taxonomy,
                    'field'             => 'term_id',
                    'terms'             => $terms[0],
                    'include_children'  => false,


        // Get all the posts having the given term from all post types
        $q = get_posts( $post_args );

        //Get the current post position. Will be used to determine next/previous post
        $current_post_position = array_search( $id, $q );

        // Get the previous/older post ID
        if ( array_key_exists( $current_post_position + 1 , $q ) ) {
            $previous = $q[$current_post_position + 1];

        // Get post title link to the previous post
        if( isset( $previous ) ) {
            $previous_post      = get_post( $previous );
            $previous_post_link = get_permalink( $previous );
            $previous_title     = '<a href="' . $previous_post_link . '">' . $previous_post->post_title . '</a></br>';

        // Get the next/newer post ID
        if ( array_key_exists( $current_post_position - 1 , $q ) ) {
            $next = $q[$current_post_position - 1];

        // Get post title link to the next post
        if( isset( $next ) ) {
            $next_post      = get_post( $next );
            $next_post_link = get_permalink( $next );
            $next_title     = '<a href="' . $next_post_link . '">' . $next_post->post_title . '</a></br>';?><pre><?php var_dump($next_title); ?></pre><?php 


        // The returned post links 
        if( isset( $previous_title, $next_title ) ) {

            $links = [
                'previous_post' => $previous_title, 
                'next_post'     => $next_title, 

        }elseif( !isset( $previous_title ) && $next_title ) {

            $links = [
                'previous_post' => 'You are currently viewing the newest post', 
                'next_post'     => $next_title, 

        }elseif( $previous_title && !isset( $next_title ) ) {

            $links = [
                'previous_post' => $previous_title, 
                'next_post'     => 'You are currently viewing the last post', 


        set_transient( $transient_id, $links, 7 * DAY_IN_SECONDS );

    return (object)$links;

add_action( 'transition_post_status', function ( $new_status, $old_status, $post )

        global $wpdb;
        $wpdb->query( "DELETE FROM $wpdb->options WHERE `option_name` LIKE ('_transient%_post_number_%')" );
        $wpdb->query( "DELETE FROM $wpdb->options WHERE `option_name` LIKE ('_transient_timeout%_post_number_%')" );

}, 10, 3 );


You can now use the code as follows in you single.php. The default taxonomy is category and post type is any. If your custom taxonomy is called mytax, you can use the code like this

if( function_exists( 'get_post_link' ) ) {          
    $post_links = get_post_link( 'mytax' );
    echo $post_links->previous_post . '</br>' . $post_links->next_post;
  • Thank you for the detailed answer! It's worthy of rep points! I tested your solution and it works perfectly. Is there a clever way to check first if a post has a category or no related posts? I'm thinking about using has_category() when using the function or get_adjacent_post() however I don't think that will work across multiple post types. – at least three characters Dec 1 '14 at 13:38
  • I'm not sure I'm getting you on this. Do you first want to check if a post has a certain category and if it has, show the next/previous post in that specific category. – Pieter Goosen Dec 1 '14 at 13:47
  • I am using if ($post_links->previous_post || $post_links->next_post) when calling the function. – at least three characters Dec 1 '14 at 13:52
  • for my question I meant check if a post has any other posts in the same category or a category at all – at least three characters Dec 1 '14 at 13:53
  • 1
    You can simply modify the function as needed. The function uses very plain links, I have intentionally left out HTML markup as to give you the freedom to modify it. I would suggest that you extend the function itself as needed. That would be the better way to go here. Just remember, if you change any code in the function, simply update any post to delete the transient so it can be reset to your changes. I hope this helps – Pieter Goosen Dec 1 '14 at 15:18

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.