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?

  • What would you like to happen when a user visits domain.com/portfolio/category/? All posts (same as /portfolio/)? Or a list of categories? Sep 11, 2014 at 16:40

2 Answers 2


That is expected and how Template Hierarchy works. I know there was a track ticket raised a couple of years ago regarding this issue if to an index for archives should be introduced in core or not. (EDIT: Found the ticket, check it out here: Ticket #13816 There should be built-in index pages for taxonomies) That idea was scrapped and will never see light in core. So there will always be that gap for an archive index page in the Template Hierarchy

You can, however, create an index page for all your separate archives with the use of page templates. For instance, create a new page, add whatever you need to add to that page, and assign it a slug of category. Now, when you visit domain.com/blog/category/, you'll see the content/index page you have created.

Apart from that, there are no other solution that I know of.


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.


  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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.