Custom Post Type with Nested Taxonomy and Template Files

I have a custom Post Type of "Portfolio" with a rewrite slug of portfolio. So the URL on the frontend is: domain.com/portfolio.

I have a custom Taxonomy of "Categories" with a rewrite slug of portfolio/category. So the URL on the frontend is: domain.com/portfolio/category/<term-slug>/

When I visit domain.com/portfolio/ the Post Type template displays just fine.

When I visit domain.com/portfolio/category/<term-slug>/ the Taxonomy term template displays just fine.

However, when I visit: domain.com/portfolio/category/ the Taxonomy template doesn't work. I get a 404 error.

I even tested on core Post Type: post and core Taxonomy: category, with the posts page set to /blog/.

Same thing...

  • domain.com/blog/ Displays posts just fine, served from home.php template.
  • domain.com/blog/category/<term-slug>/ Displays just fine, served from category.php template.
  • domain.com/blog/category/ Displays 404 error.

An alternative route I've been taking in the past is to create a "page" for those 404 errors. So for example, I have /blog/ already creates as a "page". Then I would create /blog/category/ as another page, so that I wouldn't get a 404 error when the user navigated to: domain.com/blog/category/.

Is this expected behavior? Is there a work around to not have to create pages for those "Taxonomy Archives"?

How can I have a nested Taxonomy URL and have the taxonomy.php or even archive.php template work?

Answers 1

  • Appending Taxonomy Terms to Post Type URLs

    I believe I have come up with a temporary solution from random miscellaneous sources online.

    I would love for someone to expand on my answer to make it more "bullet proof" so to speak. However, in the short run, this solution does work.


    Hook into the current rewrite rules.

    add_action('rewrite_rules_array', 'mbe_rewrite_rules_array', 100);

    Will be adding new rewrite rules to our existing rewrite rules.

    function mbe_rewrite_rules_array($rules){
        $new_rules = mbe_get_new_rewrite_rules();
        return ($new_rules + $rules);
    }
    

    Add some new rewrite rules... (This area is where I think needs some improvement for sure -- I'm not savvy with rewrite rules.)

    function mbe_get_new_rewrite_rules(){
    
        $post_type = 'mbe-portfolio';
        $post_type_slug = 'portfolio';
    
        $taxonomy = 'mbe-portfolio-categories';
        $taxonomy_slug = 'category';
    
        $slug = $post_type_slug.'/'.$taxonomy_slug;
    
        return array(
            "{$slug}/?$" => "index.php?post_type={$post_type}&taxonomy={$taxonomy}"
        );
    
    }
    

    By adding the new rewrite rule, WordPress no longer sees domain.com/portfolio/category/ as a 404, but as a Post Type Archive instead.

    Hook into the template system to display taxonomy.php when viewing domain.com/portfolio/category/

    add_filter('archive_template', 'mbe_taxonomy_template');

    function mbe_taxonomy_template($template){
    
        $templates = array();
    
        $post_type = 'mbe-portfolio';
        $taxonomy = 'mbe-portfolio-categories';
    
        if(get_query_var('post_type') == $post_type && !get_query_var('taxonomy')){
            return $template;
        }
    
        if(get_query_var('post_type') == $post_type && get_query_var('taxonomy') == $taxonomy){
            $templates[] = 'taxonomy.php';
        }
    
        $templates[] = $template;
    
        return locate_template($templates);
    
    }
    

    Now if you go to domain.com/portfolio/category/ it loads taxonomy.php from your currently active WordPress theme directory.

    So I got to thinking a bit. While taxonomy.php is loaded, rather than checking if we should display information about a Taxonomy or information about a term in the Taxonomy. I have decided to make a new template file called term.php to avoid the confusion.

    Hook into the template system to display term.php when viewing domain.com/portfolio/category/<term-slug>

    add_filter('taxonomy_template', 'mbe_term_template');

    function mbe_term_template($template){
    
        $templates = array();
    
        $queried_object = get_queried_object();
    
        if(property_exists($queried_object, 'term_id')){
            $templates[] = 'term.php';
        }
    
        $templates[] = $template;
    
        return locate_template($templates);
    
    }
    

    Now if you go to domain.com/portfolio/category/<term-slug> it loads term.php from your currently active WordPress theme directory.


    Conclusion

    1. When viewing domain.com/portfolio/ - Default archive templates are loaded as usual. (archive.php, archive-posttype.php, etc) from your currently active WordPress theme.
    2. When viewing domain.com/portfolio/category/ - The taxonomy.php template file is loaded from your currently active WordPress theme.
    3. When viewing domain.com/portfolio/category/<term-slug> - The term.php template file is loaded from your currently active WordPress theme.

    You can easily test to see if this works. All you need is three files in your currently active WordPress theme directory. (archive.php or archive-posttype.php, taxonomy.php, and term.php)

    Put this in archive.php or archive-posttype.php

    $current_post_type = get_query_var('post_type');
    
    echo '<p><strong>Current View:</strong> Post Type</p>'.PHP_EOL;
    echo '<p><strong>Current Post Type:</strong> <code>'.$current_post_type.'</code></p>'.PHP_EOL;
    

    Put this in taxonomy.php

    $current_post_type = get_query_var('post_type');
    $current_taxonomy = get_query_var('taxonomy');
    
    echo '<p><strong>Current View:</strong> Taxonomy</p>'.PHP_EOL;
    echo '<p><strong>Current Post Type:</strong> <code>'.$current_post_type.'</code></p>'.PHP_EOL;
    echo '<p><strong>Current View:</strong> <code>'.$current_taxonomy.'</code></p>'.PHP_EOL;
    

    Put this in term.php

    $current_term = get_queried_object();
    
    echo '<p><strong>Current View:</strong> Term</p>'.PHP_EOL;
    echo '<p><strong>Current Term Name:</strong> <code>'.$current_term->name.'</code></p>'.PHP_EOL;
    echo '<p><strong>Current Taxonomy:</strong> <code>'.$current_term->taxonomy.'</code></p>'.PHP_EOL;
    

    Please don't jump straight to this link. However, if you've read the entire question and accepted answer solution, and you're still confused. I've written a blog post in slightly more detail, which might help you to better understand.


Related Questions