Press "Enter" to skip to content

How to Apply a Template to Sub-categories

The big question: Does category.php apply to the children of the targeted category?

The answer: No. So now what?

For those not familiar with the power of category.php within a theme, you can catch up here. In a nutshell, you can apply a custom template to a specific category just by naming the template file with a convention that gets automatically picked up by WordPress. It’s a really awesome feature to take advantage of, but there’s one problem.

Category.php will not apply to the category’s children/sub-categories.  So then, how do we apply a template to sub-categories? After lurking around online to find forum threads like this, and brainstorming with a co-worker of mine, I found some solutions to the problem and wanted to share them.

Before we begin:

  • You should have some knowledge as to how category.php works in a theme
  • You should have some basic understanding of PHP or at the very least, have taken a look at WordPress’ Conditional Tags
  • You should have access to your theme’s files for editing purposes

Solution 1:

Editing the loop within category.php to use the conditional tags, in_category() and is_category().

Let’s imagine that you want to change the loop itself in your category.php file so that it uses your custom template when a post is in a parent category and/or it’s children.

Screen Shot 2014-08-31 at 11.02.50 PM

I’m using the Twenty-Thirteen theme for my example and in the theme’s category.php file. I’m also going to be using a parent category as depicted above, WordPress, and it’s sub-category (child), Tutorial. In my file, I’m looking for the loop to wrap my conditionals around which should look something like this before we’ve tinkered in it:

<?php /* The loop */ ?>
<?php while ( have_posts() ) : the_post(); ?>
<?php get_template_part( 'content', get_post_format() ); ?>
<?php endwhile; ?>

Since it’s just the “loop” file that I want to change, particularly, get_template_part() depending on the category, I’m going to go ahead and make a copy of content.php and called it content-custom.php. Now that we have our file, this is what we want in plain English before we put it out in code. I find that it helps when I can just say what I want before writing any logic.

If this IS the category WordPress OR if this is IN the category WordPress (it’s children), choose this custom file I created. If not (else), continue down your default route.

So let’s get writing this with code!

First we wrap get_template_part() with the if/else statement (and remove that “The Loop” comment because it bugs me):

<?php while ( have_posts() ) : the_post(); ?>
     <?php if( STUFF IS GOING HERE NEXT ) { ?>
          <?php get_template_part( 'content', get_post_format() ); ?>
     <?php } else { ?>
     <?php }//end of our if/else statement ?>
<?php endwhile; ?>

We’re going to replace “STUFF IS GOING HERE NEXT” with our conditionals: is_category(‘wordpress’) and in_category(‘wordpress’).

Then we’re going to copy and paste the get_template_part function into the “else” part of our statement. Now that we have the “else” part of our statement doing it’s default thing, we’re going to go back into the “if” part of the statement, and edit what template we want it to go to. In our case, it’s content-custom.php. Now we should have something like this:

<?php while ( have_posts() ) : the_post(); //the loop starts ?>
     <?php if( is_category('wordpress') || in_category('wordpress') ) { ?>
          <?php get_template_part( 'content', 'custom' ); //our custom template ?>
     <?php } else { ?>
          <?php get_template_part( 'content', get_post_format() ); //the existing default ?>
     <?php }//end of our if/else statement ?>
<?php endwhile; //the loop ends ?>

Okay, so this is what’s happening so far:

  1. The loop begins.
  2. Then enter the if statement: If this IS the category “wordpress” OR if this is IN the category “wordpress”, then get template part content-custom.php. Keep in mind that we’re using the slugs of the categories, hence the lowercase in “wordpress”.
  3. Else if none of these conditions are met, get template part content-whatever-post-format-matches.php which is the default.
  4. End of the loop.

Lastly, we can eliminate PHP tags we don’t need just because destroying useless PHP tags help me sleep better at night. After cleanup,  we should have this – which imo, is more readable and for the sake of seeing it pretty, I’m going to drop in a screenshot of my text-editor:

Screen Shot 2014-08-31 at 11.34.27 PM

<?php while ( have_posts() ) : the_post();
     //the loop starts
     if( is_category('wordpress') || in_category('wordpress') ){
          //if this is category wordpress or it's children, load our custom template
          get_template_part( 'content', 'custom' );
     } else {
          //if none of these conditions are met, back to default
          get_template_part( 'content', get_post_format() ); //the existing default
     }//end of our if/else statement
endwhile;//the loop ends ?>

Now this solutions works awesome, but there’s another way to explore! Let’s move on to Solution 2.

Solution 2:

Editing the loop within category.php to use the function, cat_is_ancestor_of();, in an if/else statement.

Now, just as we did in Solution 1, to save some steps, we’ll be re-using that if/else statement we wrote except replacing this:

if( is_category('wordpress') || in_category('wordpress') ){

With this instead:

if( cat_is_ancestor_of( 3, 10 ) ){

What’s going on now, is instead of using the conditional tags we had before, that generally just check whether the post either IS the category WordPress or IN the category WordPress (a sub-category), this is checking only for a sub-category, a child.

In layman’s terms, if the WordPress category ID is 3, and the Tutorial sub-category ID is 10, it’s saying:

If category 3 is the ancestor/parent of 10 – then the if statement continues, if not, then it will fall into the else part of the statement.

So it will appear as below:

<?php while ( have_posts() ) : the_post();
     //the loop starts
     if( cat_is_ancestor_of( 3, 10 ) ){
           //if category 3 (wordpress) is an ancestor (a parent) of 10 (tutorial)
           get_template_part( 'content', 'custom' );
      } else {
           //if none of these conditions are met, back to default
           get_template_part( 'content', get_post_format() ); //the existing default
     }//end of our if/else statement
endwhile;//the loop ends ?>

And if we want our custom template to apply to the parent too, we can add the “Or” logical operator and that conditional tag we used earlier. Here is what we have so far within the text editor:

Screen Shot 2014-09-06 at 5.41.51 PM
Woops, looks like I wrote “tutorials” with an s in the PHP comments, for the sake of preventing confusion, the slug of the sub-category is tutorial, not plural. No ‘s’ at the end. This has been corrected in the code I’ve pasted even though the screenshot shows that wrong.
<?php while ( have_posts() ) : the_post();
     //the loop starts
     if( cat_is_ancestor_of( 3, 10 ) || is_category('wordpress') ){
           //if category 3 (wordpress) is an ancestor (a parent) of 10 (tutorial)
           get_template_part( 'content', 'custom' );
      } else {
           //if none of these conditions are met, back to default
           get_template_part( 'content', get_post_format() ); //the existing default
     }//end of our if/else statement
endwhile;//the loop ends ?>

Now, the above works just fine, but there are two problems with our solution.

Problem 1:

This assumes that you’re checking for a specific sub-category since you’re only providing two category IDs, the parent and the child. So this might not be the best solution if there are going to be many sub-categories to check for, or the user might endlessly add more sub-categories.

Resolving Problem 1:

If you need to check for multiple sub-categories or want to ensure this works in the event that your user adds more sub-categories that you hadn’t planned for, stick with Solution 1’s code instead. It also makes for cleaner code rather than adding more “Or”s to check every single sub-category with cat_is_ancestor_of.

Problem 2:

This may or may not be a concern for some if this is your personal site or a site for a client, but the cat_is_ancestor_of function is using the category IDs. Why can that be an issue?

Well, if the site ever gets moved to another server or moved in general and it’s not using the same database, those IDs can change. And then that just breaks any logic you have assigned to these numbers.

Resolving Problem 2:

If those categories have names that likely won’t ever change, then what will future-proof our code in the event that the site moves, is ensuring that it’s using the category slugs instead, NOT the IDs, the same way we’re using the slug for is/in_category. But the cat_is_ancestor_of function only accepts IDs, not slugs. And so this is what we want to do.

  1. Get category by the slug we provide.
  2. Once we have the category, get the ID of this category.
  3. Then plug in this ID into our cat_is_ancestor_of function.

We can do all of this using the handy function, get_category_by_slug();. Before the loop starts, and staying within the PHP tags, we add in this snippet, assuming that in our case, we’re still using the parent category “WordPress” and it’s sub-category, “Tutorial”.

//getting IDs by their slugs
//storing those IDs into variables so we can use them
$wp_cat = get_category_by_slug('wordpress');
$wp_catID = $wp_cat->term_id; //the wordpress category ID
$tut_cat = get_category_by_slug('tutorial');
$tut_catID = $tut_cat->term_id; //the tutorial category ID

Once we have the IDs and stored them into variables, we can insert them into our if statement for cat_is_ancestor_of.

if( cat_is_ancestor_of( $wp_catID, $tut_catID ) || is_category('wordpress') ){

And this is the final result altogether:

Screen Shot 2014-09-06 at 6.16.12 PM

<?php 
//getting IDs by their slugs
//storing those IDs into variables so we can use them
$wp_cat = get_category_by_slug('wordpress');
$wp_catID = $wp_cat->term_id; //the wordpress category ID
$tut_cat = get_category_by_slug('tutorial');
$tut_catID = $tut_cat->term_id; //the tutorial category ID
            
while ( have_posts() ) : the_post();
     //the loop starts
     if( cat_is_ancestor_of( $wp_catID, $tut_catID ) || is_category('wordpress') ){
          //if category wordpress is an ancestor (a parent) of tutorial
          //or if this IS the category wordpress
          get_template_part( 'content', 'custom' );
     } else {
          //if none of these conditions are met, back to default
          get_template_part( 'content', get_post_format() ); //the existing default
     }//end of our if/else statement
endwhile;//the loop ends ?>

And there you have it! If you want to troubleshoot this in the event that it’s not working, feel free to echo out your variables to ensure that you’re actually getting the IDs you want. In my case, I should be getting 3 for the WordPress category and 10 for the Tutorial category. If I drop this snippet after my get slugs and IDs business, but before the loop starts, I should see this on my category archive page:

//testing 123, if you see these numbers, then you're getting stuff!
echo 'WP Cat ID:' . $wp_catID;
echo '<br/>Tutorial Cat ID:' . $tut_catID;
Well hello there category IDs, nice of you to stop by! I've got cake that I baked, please have some. :-)
Well hello there category IDs, nice of you to stop by! I’ve got cake that I baked, please have some. 🙂

 

Conclusion

Well, that was a long one! Keep in mind that you don’t necessarily have to wrap these if statements around the loop, you can wrap the entire contents of the file or wrap pieces of other files – whatever suits your needs. Hopefully this tutorial provides you the tools you need to understand how it works so you can apply the technique as you see fit.

If anyone has suggestions for alternate solutions I haven’t thought of, or ways to make this code more efficient, I’d love some feedback. I love talking about WordPress, and if I’m wrong in one of my tutorials, then hell yeah, I’ve learned something and will definitely make sure I post the result so others can learn as well.

Until next time!

One Comment

  1. Renate
    Renate May 22, 2017

    Hi this looks interesting, however i´m a noob in php and my problem is i guess i need this for my woocommerce test-site….

    the point is that i try to make restaurant menus and i came a wide way with woocommerce and use this system as a menucart but now i become an issue with a product category where i work with sub-categories.

    in real: i have a category “Getraenke” (transl: Drinks. Yes, a german project) and in this category i have sub-categorys like “Hot drinks”, “Cold drinks, and so on…

    i made a custom template from “taxonomy-product_cat.php” and name this “taxonomy-product_cat-getraenke.php”, the template works fine for just the Parent Category, but nothing happens for the sub-categorys, the system takes “taxonomy-product_cat.php”.

    i know i can make several templates like taxonomy-product_cat-cold-drinks.php, taxonomy-product_cat-hot-drinks.php, and so on but this is kind of stupid, can you help me with the right code and tell me where to write this please?

    Thanks
    Renate

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.