Display all posts from a custom post type’s categories in WordPress

Whew! That’s a long title and hints at what I was trying to do. Here’s the low down:

I was working on a WordPress site for one our clients and needed to create a custom post type (I’m not going to get into creating a custom post type, there are resources on the internet that already do a great job) so they could easily update their projects. For this custom post type, let’s call it “projects,” I created several categories so that the work could be filed and sorted easily. Some projects may also have more than one category assigned to them.

For those of you that develop with WordPress, you know that you have the option to call get_the_category() to retrieve the category of the current post, however, get_the_category() only works with the default post type. So we need to rely on get_the_terms().

What I wanted to achieve was a “related projects” section on each project page. This section would pull every project that had the same category assigned to it as the one the viewer was on. Seems simple enough, but because it’s a custom taxonomy, you have to jump through some hoops (and create a custom loop after the main loop). Also, WordPress recently added an array called tax_query that you can use in the arguments array you pass to the custom loop. That’s where the magic really happens. Here’s the final code and I’ll break down what each line is doing:


<div class="related">
    <?php // Global variable for terms
        $categories = get_the_terms($post->ID, 'project-categories');?>

<h1>
<?php foreach($categories as $category)
      if($category->slug != 'featured' && $category->slug != 'featured-widget')
      {echo $category->name;} ?> Projects
</h1>

<ul>
     <?php
         $args = array(
                'post_type' => 'project',
                'posts_per_page' => -1,
                'orderby' => 'title',
                'order' => 'ASC',
                'tax_query'=> array(
                              array(
                                  'taxonomy'=>'project-categories',
                                  'field'=>'slug',
                                  'terms'=>$category
                                 )
                              )
                           );
$loop = new WP_Query( $args );
while( $loop->have_posts() ) : $loop->the_post();
       $thumb = wp_get_attachment_image_src( get_post_thumbnail_id($post->ID), 'thumbnail' );
        $url = $thumb['0'];
?>
<a href="<?php the_permalink(); ?>">
         <li style="background:url(<?php echo $url;?>) no-repeat 0 0;">
             <h3><?php the_title(); ?></h3>
         </li>
</a>

<?php endwhile; wp_reset_postdata();?>
     </ul>
</div><!-- .related -->

Alright, let’s start from the top. Basically, the whole section is wrapped in a <div> tag with the class set to “related.” All my styling for the <ul>, <li> and <a> tags that are inside that div happens in the external CSS file. On to the PHP!


<?php // Global variable for terms
$categories = get_the_terms($post->ID, 'project-categories'); ?>

This first line sets the variable $categories to the results that are returned from get_the_terms(). This built in function takes two arguments, $post and $taxonomy. In this example, we use the built in $post->ID to get the current post, and we pass the custom category type we created when creating our custom post type: project-categories.

Our $categories variable is actually an array, as get_the_terms() returns an array no matter how many categories are returned by the current post. This is important to note and you can see what get_the_terms() returns here.

<h1>
    <?php foreach($categories as $category)
           if($category->slug != 'featured' && $category->slug != 'featured-widget')
           {echo $category->name;} ?> Projects
</h1>

This line shows why knowing what get_the_terms() returns is important. Because $categories is storing an array, we need to loop through the array (using a foreach() function) to get the information we want to retrieve and echo back out to the browser. I wanted to have a header above the related projects that says what type of projects they are. For instance, if we are viewing a car project, I want the header to say “Car Projects.” If we are on a manufacturing project, I want it to be “Manufacturing Projects.”

So this foreach() statement says: take the array stored in $categories and for each value you find, store it as it’s own variable $category. This loop and storing each value as $category comes in handy now and later when we pass arguments to our custom loop.

Within this foreach() is also an if statement. This is here to exclude certain categories from returning their results. It says, if the category slug is not ‘featured’ or the category slug is not ‘featured-widget’, echo back the rest of the results. You can see how the if statement is set up to use the data that the get_the_terms() function returns (slug is one of the data sets). Next, the line echos out the name of the category. We use name data set because it’s the formatted and “pretty” version of our category name.

<ul>
<?php
$args = array(
            'post_type' => 'project',
            'posts_per_page' => -1,
            'orderby' => 'title',
            'order' => 'ASC',
            'tax_query'=> array(
                               array(
                              'taxonomy'=>'project-categories',
                              'field'=>'slug',
                              'terms'=>$category
                               )
                              )
                            );

This is where the fun of the loop begins. I would urge you if you haven’t already, visit the WordPress Codex for information on how to create custom loops with WP_Query(), I’m going to skip over some of the finer details.

This line sets up the arguments we’ll be passing to the loop to set our query. The real magic happens in the tax_query array. You can see there are three arguments here: taxonomy, field and terms. We set the taxonomy to ‘project-categories’ because that’s the taxonomy attached to the custom post type. Next, we want to look at the category slug that’s attached to the post. Finally, we look at the terms that we want returned. And this is the magic. As I said before, we would use the $category variable again. We set terms to $category, because we only want to return items with the same slug as the current post.

In non-code speak, this is what we are telling WordPress to do for this loop (using $args): Get all posts with the custom post type of ‘projects’, show all of them, order them by title in ascending order (A,B,C, etc.) then look at all the categories associated with all posts, look at the slug field for those categories, but limit the results to this specific post’s slug.

$loop = new WP_Query( $args );
while( $loop->have_posts() ) : $loop->the_post();
$thumb = wp_get_attachment_image_src( get_post_thumbnail_id($post->ID), 'thumbnail' );
$url = $thumb['0'];
?>
<a href="<?php the_permalink(); ?>">
<li style="background:url(<?php echo $url;?>) no-repeat 0 0;">
<h3><?php the_title(); ?></h3>
</li>
</a>

<?php endwhile; wp_reset_postdata();?>
</ul>
</div><!-- .related -->

Lastly, we tackle the loop itself (which should be familiar with those of you who have worked within the loop before.

We create a variable to hold our new WP_Query() and the arguments we passed to it ($args) called $loop. We then create a while() statement that says: as long as there are posts to display ($loop->have_posts()), display them ($loop->the_post()).

In this example, I have the variable $thumb that holds the array of data returned from the built in wp_get_attachment_image_src() function. I’m using this to get the featured image from each project post, but only the thumbnail because I’m going to use it as the background of a list item.

The next line is the the variable $url which only holds the URL of the image that  wp_get_attachment_image_src() returned in it’s array.

Next, I’m simply using the built in the_permalink and the_title to echo back the link and title of each project that is returned in the query. I’m using my own styling and you will change this depending on what you want to do.

The third to last line is probably the most important: ending and surrendering the loop. First, you need to end the while() statement, simple enough – endwhile;. The next function is the biggy – wp_reset_postdata(). What this does is surrenders control back to the main loop. Depending on where you put the code from this tutorial, you may not have to reset the post data, but it’s always a good habit to do to avoid any loop related errors.

So, that’s pretty much it. Once the client’s site has launched, I’ll come back and edit this post with a link to a working example. Any questions, let me know! I’ll try and help you out.

About Chris

Chris is the senior web/graphic designer with The Simons Group. He enjoys diving into code and developing solutions to uncommon problems. He is an avid bicycle commuter and enjoys playing and recording music.

4 thoughts on “Display all posts from a custom post type’s categories in WordPress

  1. Steve

    Hi Chris,

    I’ve been searching around for how to deal with custom post types all day!

    Could you use this for a category page? I’m trying to do a similar thing, but I want to list all posts under their child categories on the category parent page. It looks like what you’ve written above could be used for this with a few tweaks?

    Cheers

  2. Chris Post author

    Hey Steve,

    Yeah, you can absolutely do that! It’s not the same code as above, it’s actually a little simpler. You’d have to modify the following to work for you (mostly in layout with getting the posts under their category title):

    <?php 
    				
    				// Starts the project loop
    				$args = array( 'post_type' => 'project', 'posts_per_page' => -1, 'orderby' => 'title', 'order' => 'ASC' );
    				
    				$loop = new WP_Query( $args );
    				
    				while( $loop->have_posts() ) : $loop->the_post(); ?>
    				
                    
    				<?php 
    				/* 
    				 * Gets the project categories to add a class to the list item
    				*/ 		$categories = get_the_terms($post->ID, 'project-categories');
    						
    				
    				/* 
    				 * Gets the thumbnail and adds it to the background of the list item 
    				*/	
    					$thumb = wp_get_attachment_image_src( get_post_thumbnail_id($post->ID), 'thumbnail' );
    					$url = $thumb['0'];
    				?>
                    
                    <li style="background:url(<?php echo $url; ?>) no-repeat 0 0;" class="<?php foreach($categories as $cat) echo $cat->slug .' '; ?>">
                    
                     	<h3 class="project-header"><a href="<?php the_permalink();? rel="nofollow">"><?php the_title(); ?></a></h3>
    				</li>
    			<?php endwhile;?>

    What this code effectively does, is creates a new WP_Query with the custom post type, you then use get_the_terms to get all of your custom categories. Once you have that information stored in an array, you can use it to loop through the category title, place the posts below that and repeat.

    For my code above, it’s creating a list item that’s a set size and uses the post’s featured image as it’s background, then puts the title (using the_title()) over the top of the image. The foreach() in the class of the li is what you would use to get your category titles.

    Hopefully that makes some sort of sense!

  3. Oleav

    Hello, thanks for such an amazing tutorial. However, I am having some troubles anyways. I am feeling so dumb, but the question is basically anything. But could you please help me to figure this out?

    Here is the part of my code I am wondering about:


    $anypost = get_posts( array(
    'post_type' => 'any'
    ) );

    $termsArray = get_the_terms( $post->ID, "category", $anypost);

    It does not simply get out all posts, so i must have done something wrong. Can you spot it? Thanks

  4. Varioshoes.Com

    Woah! I’m really enjoying the template/theme of this website.
    It’s simple, yet effective. A lot of times it’s difficult to
    get that “perfect balance” between user friendliness and visual appeal.
    I must say you have done a excellent job with this. In addition, the blog loads super quick
    for me on Opera. Excellent Blog!

Leave a Reply

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

CAPTCHA Image

*