pre_get_posts: using tax_query only for certain post type

As I can understand, cpt_tag is only assigned to the custom post type cpt. This makes things much easier and less complicated. You do not need two queries and you don’t need to merge anything

Here is the plan:

  • We need to know which terms you need to show on the homepage from the cpt post type

  • Using get_terms() and the exclude parameter, we will exclude the terms we need to show posts from from the cpt post type. The returned array from get_terms() will hold all the terms we don’t want to show. We are also going to set the fields parameter to ids so that we get an array of term ids

  • This array will be passed to the tax_query in pre_get_posts. As this is terms we need to exclude, we will set the operator parameter to NOT IN to exclude posts with these terms

You need only minor adjustments to your code to make this work

Here is an example

add_action( 'pre_get_posts', 'get_posts_plus_cpt_with_certain_tag' );

function get_posts_plus_cpt_with_certain_tag( $query ) {
    if ( $query->is_home() && $query->is_main_query() ) {

        /*
        * Change 110 to the term id you need to display posts from from cpt post type
        */
        $terms = get_terms( 'cpt_tag', array( 'exclude' => 110, 'fields' => 'ids' ) );
        if ( $terms && !is_wp_error( $terms ) ) {
            $taxquery = array(
                array(            
                    'taxonomy'  => 'cpt_tag',
                    'field'     => 'term_id',
                    'terms'     => $terms,
                    'operator'  => 'NOT IN'
                )
            );      
            $query->set('tax_query', $taxquery); 
        }   
        $query->set( 'post_type', array( 'post', 'cpt' ) );

    }
}

EDIT 1

I did not include safety, you should first check if you get any results from get_terms before using it. I have included that in my code now

EDIT 2

The above solution works, but there is a small glitch as pointed out in comments

…if I have cpt post type posts that have no cpt_tags at all, the tax_query doesn’t filter them out, it only filters out cpt post type posts with at least one cpt_tag

The problem arises in the fact that custom post type posts by default doesn’t have a default term assigned to it when no term is assigned to them in the same way as normal post type post have the term uncategorized assigned to them by default when no term is assigned to it

Because there is no relation to any term if no term is assigned to them, custom post type posts does not appear in the wp_term_relationships table, so they don’t appear in a normal tax_query

To work around this issue, we need to slightly alter our original workflow. Here is how we are going to tackle this issue

  • Get all the terms assigned to the post post type. By default, all posts from the post post type will have at least one term assigned to it unless this behavior is altered by some other drastic code. So we are 99.999% safe here.

  • We are going to use the same strategy as previous but with a slight modification. We will still use get_terms() with the fields parameter set to ids, but here we will get all the terms. I will assume that you are using the default build-in taxonomy category for the post post type.

  • We can simply just pass the term ids we need to include for the cpt_tag to our tax_query, nothing special here

  • Remember to set the relation parameter to OR as we need posts with all terms in category taxonomy or all posts that has the specific term from the cpt_tag taxonomy. Omitting the relation parameter will default to AND which will fail your query as it will look for posts that have terms in category taxonomy and cpt_tag taxonomy

So lets put that in code:

(I will again use term id 110 for the term for cpt_tag, just change accordingly)

add_action( 'pre_get_posts', 'get_posts_plus_cpt_with_certain_tag' );

function get_posts_plus_cpt_with_certain_tag( $query ) {
    if ( $query->is_home() && $query->is_main_query() ) {

        /*
        * Change 110 to the term id you need to display posts from from cpt post type
        */
        $terms = get_terms( 'category', array( 'fields' => 'ids' ) );
        if ( $terms && !is_wp_error( $terms ) ) {
            $taxquery = array(
                'relation' => 'OR',
                array( // Includes all terms (categories) for post post type          
                    'taxonomy'  => 'category',
                    'field'     => 'term_id',
                    'terms'     => $terms,
                ),
                array( // Include the terms you need to display from cpt_tag           
                    'taxonomy'  => 'cpt_tag',
                    'field'     => 'term_id',
                    'terms'     => array( 110 ),
                ),

            );      
            $query->set('tax_query', $taxquery); 
        }   
        $query->set( 'post_type', array( 'post', 'cpt' ) );

    }
}