Display all posts in a custom post type, grouped by a custom taxonomy

I’m working on a member page where I use a custom post type with a custom taxonomy. My custom post type is called member and my custom taxonomy is called member_groups.

I want to list all the members but group them together into their respective groups.

Read More

So to be clear, I’ve 35 members divided into 9 groups – so instead of making the same query nine times I want to do it once but group them together, so that Member1, Member4 and Member 11 is grouped together in one group, called “Marketing”.

I’m using WP_Query to retrieve all posts under post type member. I’ve tried different attempts but with no successful result.

How can I achieve that?

Related posts

Leave a Reply

7 comments

  1. So, you might consider automating the multiple queries.

    First, get the list of terms in your custom taxonomy, using get_terms():

    <?php
    $member_group_terms = get_terms( 'member_group' );
    ?>
    

    Then, loop through each one, running a new query each time:

    <?php
    foreach ( $member_group_terms as $member_group_term ) {
        $member_group_query = new WP_Query( array(
            'post_type' => 'member',
            'tax_query' => array(
                array(
                    'taxonomy' => 'member_group',
                    'field' => 'slug',
                    'terms' => array( $member_group_term->slug ),
                    'operator' => 'IN'
                )
            )
        ) );
        ?>
        <h2><?php echo $member_group_term->name; ?></h2>
        <ul>
        <?php
        if ( $member_group_query->have_posts() ) : while ( $member_group_query->have_posts() ) : $member_group_query->the_post(); ?>
            <li><?php echo the_title(); ?></li>
        <?php endwhile; endif; ?>
        </ul>
        <?php
        // Reset things, for good measure
        $member_group_query = null;
        wp_reset_postdata();
    }
    ?>
    

    I can’t see anything particularly wrong with this approach, though it may have a limited ability to scale (i.e. if you have hundreds or thousands of members, or member_group terms, you may see performance issues).

  2. I found a solution by using a custom query and then grouping it with the term name:

    SELECT * 
    FROM wp_term_taxonomy AS cat_term_taxonomy
    INNER JOIN wp_terms AS cat_terms ON cat_term_taxonomy.term_id = cat_terms.term_id
    INNER JOIN wp_term_relationships AS cat_term_relationships ON cat_term_taxonomy.term_taxonomy_id = cat_term_relationships.term_taxonomy_id
    INNER JOIN wp_posts AS cat_posts ON cat_term_relationships.object_id = cat_posts.ID
    INNER JOIN wp_postmeta AS meta ON cat_posts.ID = meta.post_id
    WHERE cat_posts.post_status =  'publish'
    AND meta.meta_key =  'active'
    AND meta.meta_value =  'active'
    AND cat_posts.post_type =  'member'
    AND cat_term_taxonomy.taxonomy =  'member_groups'
    

    Then by just using a regular foreach query I can just extract the information I want.

    But I’m still interested in another way if there is, maybe by using WordPress’ own functions.

  3. even simpler:

    $terms = get_terms('tax_name');
    $posts = array();
    foreach ( $terms as $term ) {
        $posts[$term->name] = get_posts(array( 'posts_per_page' => -1, 'post_type' => 'post_type', 'tax_name' => $term->name ));
    }
    

    Within the resultant $posts array, each tax term is the key to a nested array containing its posts.

  4. I had this exact need, and Chip’s solution worked, except for one thing: 'field' => 'slug' is required.

        foreach ( $service_categories as $category ) {
            $services = new WP_Query( 
                array(
                    'post_type'     => 'service',
                    'tax_query'     => array(
                        array(
                            'taxonomy'  => 'service_category',
                            'terms'     => array( $category->slug ),
                            'operator'  => 'IN',
                            'get'       => 'all',
                            'field'     => 'slug'
                        )
                    )
                ) 
            ); ?>
            <h2><?php echo $category->slug; ?></h2>
            <?php if ( $services->have_posts() ) {  // loop stuff goes here ?>
    

    I also needed the resulting display to be flat, so 'get' => 'all' is set here.

    Hopefully this helps somebody else out.

  5. I had to do this on a project years ago. Similar answer to djb, just with a bit more details. This will output all of your taxonomy names as an h3, with a bulleted list of each post title linked to their detail page.

    <?php // Output all Taxonomies names with their respective items
    $terms = get_terms('member_groups');
    foreach( $terms as $term ):
    ?>                          
        <h3><?php echo $term->name; // Print the term name ?></h3>                          
        <ul>
          <?php                         
              $posts = get_posts(array(
                'post_type' => 'member',
                'taxonomy' => $term->taxonomy,
                'term' => $term->slug,                                  
                'nopaging' => true, // to show all posts in this taxonomy, could also use 'numberposts' => -1 instead
              ));
              foreach($posts as $post): // begin cycle through posts of this taxonmy
                setup_postdata($post); //set up post data for use in the loop (enables the_title(), etc without specifying a post ID)
          ?>        
              <li><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></li>    
            <?php endforeach; ?>
        </ul>                                                   
    <?php endforeach; ?>
    
  6. $query = new WP_Query( 
       array ( 
          'post_type' => 'member', 
          'orderby'   => 'meta_value', 
          'meta_key'  => 'member_group' 
       ) 
    );
    

    Then when you loop through this query you could just use an if along these lines
    (in php pseudocode)

    $groupName = "";
    $counter = 0;
    if havePosts: while havePosts: thePost
    
    if( $groupName != post->meta_value )
    {
    if ($counter > 0)
    {
    </ul>
    }
    <h1>A group name</h1>
    <ul>
    <li>member name</li>
    }
    else
    {
    <li>member name</li>
    }
    
    endwhile;endif
    
    </ul>
    

    I hope that helps. I think you were making this far more complicated than it needed to be.

    More information: http://codex.wordpress.org/Class_Reference/WP_Query#Taxonomy_Parameters

  7. Well, it’s an old thread, but if someone passes by as I did, this might help.
    The idea is to modify the main query so we don’t need to go the templates and generate new queries and loops…

    PS: Yet to be tested in large dbs. It was satisfactory in my case.

    function grouped_by_taxonomy_main_query( $query ) {
    
        if ( $query->is_home() && $query->is_main_query() ) { // Run only on the homepage
    
            $post_ids = array();
    
            $terms = get_terms('my_custom_taxonomy');
    
            foreach ( $terms as $term ) {
                $post_ids = array_merge( $post_ids, get_posts( array( 
                    'posts_per_page' => 4, // as you wish...
                    'post_type' => 'my_custom_post_type', // If needed... Default is posts
                    'fields' => 'ids', // we only want the ids to use later in 'post__in'
                    'tax_query' => array( array( 'taxonomy' => $term->taxonomy, 'field' => 'term_id', 'terms' => $term->term_id, )))) // getting posts in the current term
                );
            }
    
            $query->query_vars['post_type'] = 'my_custom_post_type'; // Again, if needed... Default is posts
            $query->query_vars['posts_per_page'] = 16; // If needed...
            $query->query_vars['post__in'] = $post_ids; // Filtering with the post ids we've obtained above
            $query->query_vars['orderby'] = 'post__in'; // Here we keep the order we generated in the terms loop
            $query->query_vars['ignore_sticky_posts'] = 1; // If you dont want your sticky posts to change the order
    
        }
    }
    
    // Hook my above function to the pre_get_posts action
    add_action( 'pre_get_posts', 'grouped_by_taxonomy_main_query' );