Exclude sub category posts from category display

I have a post category structure like this:

  • news
  • news / conferences
  • news / newsletter

In Appearance > Menu, I have added 3 menu items.

Read More

One each for the categories News, Conferences, Newsletter.

However, the News page also displays posts from Conferences and Newsletter.

How do I exclude posts from subcategories from being displayed in the parent category’s page?

Thanks

Related posts

6 comments

  1. I rewrote the code from a post at WP Engineer:

    function wpse_filter_child_cats( $query ) {
    
    if ( $query->is_category ) {
        $queried_object = get_queried_object();
        $child_cats = (array) get_term_children( $queried_object->term_id, 'category' );
    
        if ( ! $query->is_admin )
            //exclude the posts in child categories
            $query->set( 'category__not_in', array_merge( $child_cats ) );
        }
    
        return $query;
    }
    add_filter( 'pre_get_posts', 'wpse_filter_child_cats' );
    

    Give it a try by pasting the snippet in your functions.php. Please keep in mind that a post can’t belong to both parent category and child category, or you won’t get it displayed.

  2. Use category__in instead of cat in your query.

    As you know already, cat will query a category and it’s sub categories:

    $category = get_category (get_query_var('cat'));
    WP_Query( 'cat'=>$category, 'post_type' => 'post' );
    

    However, when using category__in, you’ll will not get results from the subcategory.

    $category = get_category (get_query_var('cat'));
    WP_Query( 'category__in'=>$category, 'post_type' => 'post' );
    

    The documentation on querying based on categories is here.

  3. If you want to include any and all posts that have the category that is currently being queried, you can use the following. This could potentially include posts that have sub/child categories of the queried category, but only if they also have the parent category. It will not include posts that only have a child category. In other words, given the following:

    • Category 1

    • Category 2, which is a child category of category 2

    • Post A, which has category 1 only

    • Post B, which has category 2 only

    • Post C, which has both category 1 and category 2

    this query will return posts A and C, but not B

    function cat_query_filter($query) {
        if (!is_admin() && $query->is_main_query() && is_category()) {
            $queried_cat_obj = get_queried_object();
            $queried_cat_id = $queried_cat_obj->term_id;
            $query->set('category__in', $queried_cat_id);
        }
    }
    add_action('pre_get_posts', 'cat_query_filter');
    

    The answer provided by @createscape is a good one, but would exclude posts like post B, which may or may not be what you want. So, depending on what you want to be included in your list of results, you could either use that solution or this one. Additional info here: https://developer.wordpress.org/reference/classes/wp_query/#category-parameters

  4. That works well and prevents posts in child categories from showing up in the parent category listing. However, make sure and have a post in that parent category, or you’ll get a not found error if someone clicks on the parent category menu item.

  5. I wrote my own function in order to exclude subcategory posts from the loop, as I found the code above didn’t work for me.

    In my theme archive.php file, above the loop, I list the subcategories:

        <?php
           $current_cat = get_queried_object();
    
           $args = array( 'parent'=>$current_cat->term_id, 'child_of' => $current_cat->term_id, );
            $categories = get_categories( $args );
            foreach($categories as $category) { ?>
    
               <h2><?php echo $category->name ;?></h2>
               <p> etc....</p>
          <?php } ?>
    

    In my functions.php file, I’ve added the following custom function using pre_get_posts:

    add_action( 'pre_get_posts', 'main_query_without_subcategory_posts' );
    
    function main_query_without_subcategory_posts( $query ) {
    
    if ( ! is_admin() && $query->is_main_query() ) {
        // Not a query for an admin page.
        // It's the main query for a front end page of your site.
    
        if ( is_category() ) {
    
       //Get the current category
            $current_category = get_queried_object();
            //get the id of the current category
            $current_cat_id = $current_category->term_id;
    
            //find the children of current category
            $cat_args = array( 'parent'=>$current_category->term_id, 'child_of' => $current_category->term_id, );
            $subcategories = get_categories( $cat_args );
    
            //Get a list of subcategory ids, stick a minus sign in front
            $subcat_id = array();         
            foreach($subcategories as $subcategory) { 
                $subcat_id[] = " -". $subcategory->term_id; 
            }
    
            //join them together as a string with a comma seperator          
            $excludesubcatlist = join(',', $subcat_id);
    
           //If you have multiple parameters, use $query->set multiple times
            $query->set( 'posts_per_page', '10' );
            $query->set( 'cat', ''.$current_cat_id.','.$excludesubcatlist.'' );
          }
        }
      }
    

    Then in the archive.php, below the subcategories, I’ve added the regular WordPress loop which is now being modified by the above function:

    <?php  while (have_posts() ) : the_post(); ?>
         <h2><?php the_title();?></h2>
         <p> etc....</p>
     <?php endwhile;?>
    

    Though the WordPress codex says that using “category__in” will exclude posts from subcategories, that didn’t work for me and subcategory posts were still showing.

    https://codex.wordpress.org/Class_Reference/WP_Query#Category_Parameters

    https://developer.wordpress.org/reference/hooks/pre_get_posts/

  6. Okay, as of 2019 I had to use slightly modified code and it works with any taxonomy. Not only with standard category. I used it with my setup of woocommerce and wp, preventing products from nested categories being displayed in parent cat:

    add_action('parse_tax_query', 'wpse_filter_child_cats'); 
    
    function wpse_filter_child_cats( $query ) {
        if ( is_tax( 'product_cat' ) ) {
    
            $queried_object = get_queried_object();
            $child_cats     = (array) get_term_children( $queried_object->term_id, 'product_cat' );
    
            if ( ! $query->is_admin ) //exclude the posts in child categories
            {
    
                $query->tax_query->queries[] = [
                    'taxonomy' => 'product_cat',
                    'field'    => 'term_id',
                    'terms'    => array_merge( $child_cats ),
                    'operator' => 'NOT IN'
                ];
            }
    
        }
    
    }
    

Comments are closed.