WordPress Custom Taxonomy With Same Slug As Custom Post Type

If you want the Terms in your Custom Taxonomy to have their URL path containing their Custom Post Type slug instead of Custom Taxonomy slug without showing 404 page, that can be a little tricky, and as I found throughout the blogs and forums quite impossible.

But, fortunately, it is possible! And it is really quite simple (although it took me a whole day and a lot of nerves to figure it out).

Before going any further, note that this only works when Permalink Settings are set to Post name!

Custom Post Type and Custom Taxonomy

Lets imagine you have situation like this, you need Custom Post Type named Recipes, and you need dynamic categories (TermsChinese, Indian, Thai… So you don’t want to create Custom Taxonomy for each and every one of them, you want them to be categories inside Taxonomy, so you can create as many as you wish.

And you want to achieve URL structure like this:

http://domain.com/recipes/
http://domain.com/recipes/chinese/
http://domain.com/recipes/indian/
http://domain.com/recipes/thai/

The logical way to do it would be like this:

// register custom post type
function create_post_types() {
    register_post_type('recipes', array(
        'labels' => array(
            'name' => 'Recipes',
            'all_items' => 'All Posts'
        ),
        'public' => true
    ));
}
add_action('init', 'create_post_types');

// register taxonomy
function create_taxonomies() {
    register_taxonomy('recipes-categories', array('recipes'), array(
        'labels' => array(
            'name' => 'Recipes Categories'
        ),
        'show_ui' => true,
        'show_tagcloud' => false,
        'rewrite' => array('slug' => 'recipes')
    ));
}
add_action('init', 'create_taxonomies');

Note that when registering Custom Taxonomy we use rewrite slug same as the Custom Post Type slug.
This is important!

Then lets create some Terms in admin for that Custom Taxonomy, as we stated above (Chinese, Indian, Thai…).

Rewrite rules

That would give us the links we wanted. But now, WordPress wouldn’t exactly know what to do with them, cause Custom Types and Taxonomies may not have the same slug, and would give 404 page on those taxonomy terms you created. So, now our function comes into play:

/*
 * Replace Taxonomy slug with Post Type slug in url
 * Version: 1.1
 */
function taxonomy_slug_rewrite($wp_rewrite) {
    $rules = array();
    // get all custom taxonomies
    $taxonomies = get_taxonomies(array('_builtin' => false), 'objects');
    // get all custom post types
    $post_types = get_post_types(array('public' => true, '_builtin' => false), 'objects');
    
    foreach ($post_types as $post_type) {
        foreach ($taxonomies as $taxonomy) {
	    
            // go through all post types which this taxonomy is assigned to
            foreach ($taxonomy->object_type as $object_type) {
                
                // check if taxonomy is registered for this custom type
                if ($object_type == $post_type->rewrite['slug']) {
		    
                    // get category objects
                    $terms = get_categories(array('type' => $object_type, 'taxonomy' => $taxonomy->name, 'hide_empty' => 0));
		    
                    // make rules
                    foreach ($terms as $term) {
                        $rules[$object_type . '/' . $term->slug . '/?$'] = 'index.php?' . $term->taxonomy . '=' . $term->slug;
                    }
                }
            }
        }
    }
    // merge with global rules
    $wp_rewrite->rules = $rules + $wp_rewrite->rules;
}
add_filter('generate_rewrite_rules', 'taxonomy_slug_rewrite');

WordPress has its own nice rewriting mechanism independent from .htaccess that is processed in php before output. Here we tell WordPress what to do with the URLs we created (only those Taxonomy Terms whose parent Taxonomy has the same slug as its Custom Post Type) and where to redirect them, by adding permalinks to his rewriting object.

PHP files

So, now, all you have to do is to create .php files in your theme directory, that will print the content. The filename for the Post Type page is arhchive-{post_type}.php, for the Taxonomy page is taxonomy-{registered_taxonomy}.php, and for the single pages its single-{post_type}.php

In our case from above it would be:

  • archive-recipes.php
  • taxonomy-recipes-categories.php
  • single-recipes.php

Permalinks

After doing all this, just go to settings/permalinks and save changes using structure /%postname%/, and thats it!

Download example files

Download working example to see how it works.

74 comments

  1. Jkruger

    Didn’t work for me. Same setup. Still 404s

    1. I forgot to write how to name your files in theme directory. I’ve added it to the text above, so try that.

    2. Steve

      Try to regenerate permalinks (Settings->Permalinks->Save changes)…

      1. Thanks, that worked for me. Kept checking to make sure file names were ok and everything, when in fact a permalink re-save was the solution.

  2. I’ve been looking for this and it almost works. The problem I’m having is that now single items of the post type shup up 404.

    In in my case I have a Custom post type of “Products”, with a custom taxonomy of “Product Type”.

    I want taxonomy archives to show up under /products/taxonomy-term/, and this bit you have achieves that. But now my single products with urls like /products/single-product-name/ are 404.

    Back to searching…

    1. Like I said to JKRUGER, try to name your files like I’ve mentioned in the text, it should be working then.

  3. Peter

    Didn’t work for me either. Tried with CPT: project and project-categories with slug: project.

    :/

    1. I’ve included working example at the end of the text, download it and try it ;)

  4. Thanks for this.

    I too bumped into the issue of categories showing while the projects gave a 404. I noticed the rewrite rules were cached… and this could be the issue if you experience similar problems.

    Flush the rewrite rules (I did it by re-saving the permalink setting). This should fix the problem.. it did for me.

  5. I’m also struggling a bit. I have a custom post type called retail-product and a custom post type called retail-product-type. I want the both to share a base called retail-products. However, in the case of the custom post type, I want it to be /retail-products/%retail-product-type%/. I keep getting a 404 on the base /retail-products/. Can the function be modified in some way to cater for the /%retail-product-type%/ ?

    1. You got me confused. You have two CPTs and want both to share same base? Or I misunderstood something?

      1. Thanks for the reply. I’m actually trying something odd. I don’t have two CPT trying to share the same base as you asked. I have one CPT and one Custom Taxonomy trying to share the same base, except that I need the CPT slug to be “shared-base/%Custom-Taxonomy%”. I’m sure I’m confusing you even more. The end result would be an archive of the Custom Taxonomy items. Don’t worry if you can’t assist, as your function has already done half the work to eliminate some 404’s. I can always create a plain page so the base slug has something to match.

          1. Have a look at my code:
            https://gist.github.com/charlimmelman/7c901bea36c756961b47
            You will see three functions. One for the CPT, one for the Taxonomy and one to rewrite the %retail-product-types% part of the permalink when I create a new post.

            So, without the slug, it should work something like:
            http://yourdomain.com/retail-product-types/strong-coffees/blah-coffee-brand. But I am specifying in the slug parameter that I want it to be …com/retail-products/strong… instead.

            So, I would like:
            /retail-products – Tax Archive
            /retail-products/strong-coffees – CPT archive

            Your function works all but for /retail-products where I get a 404. I have created a page called “Retail Products” which fills that gap. It’s ok, but I kinda wanted it done right.

            This comment is taking up space, so feel free to email me directly.

  6. Hello! Thanks for the very helpful article. I have one question though: Using your example with recipes. Let’s say my website has both a BLOG and a RECIPE section. Here, you explain how to make each term a valid/non404 URL. But, what if I had “chinese” or “indian” as a tag on blog posts, as well? The tag (if I made that first) would already own the “chinese” or “indian” slug, correct? So, WordPress would automatically add a “-2” to my slugs, right? However, I would like nice, clean URLs to these recipe terms; how can I acheive this?

    For example, a year ago I wrote a blog post about eating Chinese food, tagging the article with “chinese,” among other things. Now, I did what you did in your article – I have made a custom post type “recipes,” and a custom tax for the recipe categories. But I want my URLs to look like: http://www.domain.com/recipes/chinese NOT domain.com/recipes/chinese-2/ you know?

    Any ideas as to what I should do? Thanks a bunch for the help and for the very informative article!

  7. Cool it is working great busy getting it implemented in a blog with a directory currently

  8. This does seem to work, but it also seems to ruin pagination. Any thoughts on how to fix?

    1. What plugin are you using for pagination? We’ve tested it with WP-PageNavi and it worked fine.

      1. Jung

        Hi I tried using WP-PageNavi but it still navigates to single-posttypename.php file (with a first item within the list)
        can you please help?

  9. Lee

    Hi Željko,
    thanks for taking the time to share your experience and creating the example files. For the life of me I can’t get the URL’s to play nice. With your sample files I create a project category (let’s call it ‘urban’). I then create a new project (let’s call it ‘city’) and associate it to the new category. The URL in the new project stays as /project/city, not /project/urban/city as I would expect it to be. I have resaved the permalinks and still nothing. What am I missing?

    Thanks, any suggestions are appreciated :)

    Lee

    1. Just let me get this clear, you registered custom post type as ‘project’ like this:
      register_post_type('project', ... );
      and taxonomy as ‘urban’ like this:
      register_taxonomy('urban', array('project'), ... );
      And ‘city’ is the term you created in administration?
      Have I understood you correctly?

      1. Lee

        Thanks for your reply. All I have done is restored your test files as a theme. In WordPress admin I have then configured a project category called ‘urban’, then added a new project ‘city’ and associated it to the category ‘urban’. I haven’t changed any code. Should I expect to get a URL for city like /project/urban/city?

  10. Clive

    Thank you for this. It’s great however it throws a 404 when using pagination, example:

    http://domain.com/recipes/indian/page/2/

    Any ideas from you clever folk how to get it working with pagination?

    1. got the same problem, anyone got this right ?

  11. Didn’t work for me though. Post type was “music” and register_taxonomy (‘genre’ ….

    I had xyz.com/music/genre/post-title
    url structure worked fine but just simply didn’t show the posts (archive-music.php) rather it gives me 404 notice.

    Interestingly, I could get the archive-music.php working with custom loop.. ( ‘music’, ‘posts_per_page’ => 10 );
    $loop = new WP_Query( $args );” . Regular loops doesn’t work! Any idea? I also want to display posts under “taxonomies” but “taxonomy-music-genre.php” fails to grab any post with regular loop. Please help. Thanks.

  12. Theo Ephraim

    worked for me. Thanks. But your code does not take into account a rewrite slug on the custom post type (for example my post type is portfolioitem but the rewrite slug is just “portfolio”)
    Here is a slightly updated version which takesit into account.

    function ss_fix_portfolio_rewrite($wp_rewrite) {
        $rules = array();
     
        // get all custom taxonomies
        $taxonomies = get_taxonomies(array('_builtin' => false), 'objects');
     
        // get all custom post types
        $post_types = get_post_types(array(
            'public' => true,
            '_builtin' => false),
        'objects');
     
        foreach ($post_types as $post_type) {
        	$post_type_name = $post_type->name;
        	$post_type_slug = $post_type->name;
        	if ( $post_type->rewrite && $post_type->rewrite['slug'] ){
        		$post_type_slug = $post_type->rewrite['slug'];
        	}
        	
            foreach ($taxonomies as $taxonomy) {
     
                // check if taxonomy is registered for this custom type
                if ($taxonomy->object_type[0] == $post_type_name) {
     
                    // get all categories (terms)
                    $categories = get_categories(array(
                        'type' => $post_type_name,
                        'taxonomy' => $taxonomy->name,
                        'hide_empty' => 0
                    ));
     
                    // make rules
                    foreach ($categories as $category) {
                      $rules[$post_type_slug . '/' . $category->slug . '/?$'] = 
                        'index.php?' . $category->taxonomy . '=' . $category->slug;
                    }
                }
            }
        }
     
        // merge with global rules
        $wp_rewrite->rules = $rules + $wp_rewrite->rules;
    }
    add_filter('generate_rewrite_rules', 'ss_fix_portfolio_rewrite');
    1. Thanks man, I totally forgot about that. I’ve updated the code slightly different from yours, with one more modification (fix).

      Cheers ;)

    2. Cameron

      Thanks Theo,

      I hit this same issue and it took me a while to realize that neither the original post nor the attached sample zip handled CPT->rewrite[‘slug’]. I just needed to replace $rules[$post_type->name…] with $rules[$post_type->rewrite[‘slug’]…]

      Regards!

    3. Nico

      Awesome! Thank to you both!

  13. EY

    Hi I have problem with the URL rewrite, I have template files:
    – archive-articles.php
    – taxonomy-articles-categories.php

    and this is my cpt and tax slug:
    – my cpt slug = articles
    – my taxonomy slug = articles

    and I want my URL to be like this:
    http://domain.com/articles (works)
    http://domain.com/articles/parent-tax (works)
    http://domain.com/articles/parent-tax/child-tax (404 error)
    http://domain.com/articles/parent-tax/child-tax/post-title (404 error)

    Thanks,

    1. reeslo

      /**
      * Replace Taxonomy slug with Post Type slug in url
      *
      * Version: 1.1 - http://someweblog.com/wordpress-custom-taxonomy-with-same-slug-as-custom-post-type/
      */
      public function taxonomy_slug_rewrite($wp_rewrite) {

      $rules = array();
      // get all custom taxonomies
      $taxonomies = get_taxonomies(array('_builtin' => false), 'objects');
      // get all custom post types
      $post_types = get_post_types(array('public' => true, '_builtin' => false), 'objects');

      foreach ($post_types as $post_type) {
      foreach ($taxonomies as $taxonomy) {

      // go through all post types which this taxonomy is assigned to
      foreach ($taxonomy->object_type as $object_type) {

      // check if taxonomy is registered for this custom type
      if ($object_type == $post_type->rewrite['slug']) {

      // get category objects
      $terms = get_categories(array('type' => $object_type, 'taxonomy' => $taxonomy->name, 'hide_empty' => 0));

      // make rules
      foreach ($terms as $term) {

      $route = '';

      if(!empty($term->parent)) {

      $route .= $object_type . '/' . $this->get_term_parents_route($term->parent, $term->taxonomy) . '/'. $term->slug . '/?$';
      }
      else {
      $route .= $object_type . '/' . $term->slug . '/?$';
      }

      $rules[$route] = 'index.php?' . $term->taxonomy . '=' . $term->slug;
      }
      }
      }
      }
      }
      // merge with global rules
      $wp_rewrite->rules = $rules + $wp_rewrite->rules;
      }

      public function get_term_parents_route($id, $taxonomy, $route = array()) {

      $term = get_term_by('id', $id, $taxonomy);

      if(!empty($term)) {

      $route[] = $term->slug;

      if(!empty($term->parent)) {
      $route[] = $this->get_term_parents_route($term->parent, $term->taxonomy);
      }
      }

      $reverse = array_reverse($route);

      $route = implode('/', $reverse);

      return $route;
      }

      1. Arun

        how to create parent child URL structure with same slug

  14. Could you add rules for paged archives ($rules[$object_type . ‘/’ . $term->slug . ‘/page/?([0-9]{1,})/?$’] = ‘index.php?’ . $term->taxonomy . ‘=’ . $term->slug . ‘&paged=$matches[1]’;) and for category feeds?

    Thanks!

  15. Cameron

    I was having the same issue with my single pages returning 404 errors when the archive and taxonomy archive pages were working just fine. I was even able to copy the example “project” CPT and templates into my own project to verify they worked in the same context.

    The one difference I noticed was order of definition. I was registering my custom taxonomy before my CPT whereas the example in this post does the reverse. For whatever reason I have yet to figure out, this order of definition matters. Once I switched my project to register the CPT before the taxonomy, like magic it worked.

    If I manage to figure out why, I’ll post what I find. (or if anyone else figures it out, please share! My ocd brain is dying to know.)

  16. Works perfectly in the scenario stated and explains a lot of headaches in the past with CPTS.

    I didn’t realise it was because the slugs were shared. I usually create a page to fulfil the purpose of the /post_type/ url with the CPT archive and query_var settings off and then the taxonomy permalinks behave as expected. Will try this next time.

    The CMS allways seems to generate permalinks in the expected format; why doesn’t the front-end just rewrite the URLS in the same way? Can your filter be modified to work without setting permalink settings to post_name?

  17. leonardo

    it’s amazing, man! i was searching exactly for this! thanks so much!

    1. leonardo

      but i have a issue!

      cpt = ‘house’

      tax = ‘house-types’

      url domain.com/house/ – works (list all houses)
      ul domain.com/house/sale/ – works (list all sale house)

      but my permalink appears – domain.com/house/texas-united-states

      404 error!

      why?

      1. Augusto Carmo

        did you find the answer for that?

  18. Thanks for the PHP file tips. Finaly got the custom templates to work.

  19. Thank you so much for sharing this snippet! Other techniques were a lot more cumbersome and didn’t work with pre-existing WP functions. I’m glad to see someone has figured it out.

  20. Thanks, work perfecly for me.

    But, how to show all taxonomies althought they are not have post?

  21. Hi, I couldn’t quite work out the:

    if ($object_type == $post_type->rewrite[‘slug’])

    The object_type was coming out as my namespaced post type name and wasn’t matching the slug so I changed this to be:

    if ($taxonomy->rewrite[‘slug’] == $post_type->rewrite[‘slug’])

    Can you see any complications coming from that in the future?

  22. Hello,
    Your code is very usefull. But it does not work with sub categories. Do you know how to fix that?

    1. xchp

      I too would like to know how to make your code display sub-categories.

  23. Michel

    thanks Much. It works great. But I have a little issue on the page navigation. When I navigate to page/2 it works fine. But when I navigate to page 3 and so on, I got 404 page. Any thoughts? Thanks

  24. wntd!

    awesome, that rewrite code fix all my problems, thanks!!

  25. The fix from @THEOEPHRAIM works for me, but… I’m having problem with subcategories!
    /products/subcategory … returning 404 error. It should be /products/category/subcategory

    Any ideas?

  26. Jason

    You are the man. Thanks.

  27. Pudge Reyem

    When adding a new taxonomy I needed to manually update the Rewrite rules, but automated this using the save_post hook. Code here; http://pudge.se/D9Vs

  28. Gen

    You are a genius!! I spent all day trying to figure this out and I finally found your post. Thank you!

  29. Greg,

    Thanks! I’m just building my first theme, and this finally got my custom post types & taxonomies working. One question though: From an SEO perspective, to have URLs like this:

    http://domain.com/recipes/
    http://domain.com/recipes/chinese/
    http://domain.com/recipes/chinese/peking-duck

    is considered (by Google) as less preferrable to this:

    http://domain.com/recipes/
    http://domain.com/chinese/
    http://domain.com/peking-duck

    Is this possible to accomplish? Would this just be a matter of customizing the “rewrite slug” method?

  30. I got this error, when i try to use your function,

    Parse error: syntax error, unexpected ‘$rules’ (T_VARIABLE)

    Basicaly error is because of the line “$rules = array();”

    please suggest me what to do..

    1. Be carefull when copying and pasting from the text box on the page as it sometimes will convert characters.
      I realized this when I removed the first $rules variable and noticed that I was getting PHP errors with &nbps; at the line where I had spaces in the code.

  31. How to prevent a post to steal a term slug?
    Example : I have a ‘books’ CPT with a ‘novels’ category:
    URL –> http://www.tld.com/books/novels
    What can I do if I don’t want any of my post can have the same URL to prevent conflicts ? Is it possible to deal with a filter to attribute a modified slug to a post that can try to use a reserved term slug ? Thank you

    1. Porter

      I’ve been trying to get this to work all night, and for whatever reason, site.com/dining/actual-post is giving me a 404. site.com/dining and site.com/dining/term work great, but all posts of the custom post type return a 404. I’ve made the template, I’ve flushed the rewrite rules, nothing seems to work – any ideas?

  32. Samuel

    Thanks for tutorial.
    I get “Page not found” when open sub category.

    For example : I have “gallery” custom post type. The slug of custom taxonomy is “gallery” too.

    I create a post and attach it in a category, named “holiday”.

    I am able to display post list with the following link : ../gallery/holiday/

    Then I create a sub category “beach” underneath “holiday”

    I get “Page not found” when open this link :
    ..gallery/holiday/beach/

    How to modify your script to solve the problem?

    Thanks in advance.
    Samuel

  33. nicmare

    very, very good tutorial! i do not need the taxonomy theme file. but added has_archive = true to my CPT. then it worked as expected for me. thank you!

  34. nicmare

    you really need to flush the rewrite rules cache. but has someone an idea how to do this when a new taxonomy is created? so i do not need to do this each time by visiting permalinks page?!

    1. nicmare

      this should do the trick:
      add_action( ‘save_post’, ‘flush_permalinks’);
      function flush_permalinks( $post_id ) {
      if(get_post_type($post_id) == “YOUR_POSTTYPE_NAME”){
      flush_rewrite_rules();
      }
      }

  35. Ryan

    This worked a charm for me! Exactly what I wanted. :)

  36. Andrius

    Hi there is a small problem with line 26. The code works perfectly but if the amount of posts is higher it cant won’t allow you to navigate to next page. Additional line helps to fix this issue :
    $rules[“{$object_type}/{$term->slug}/page/?([0-9]{1,})/?$”] = “index.php?{$term->taxonomy}={$term->slug}&paged=” . ‘$matches[1]’;

  37. Ask a “main street” American how the economy is doing and they’ll say ‘we’re in a recession, if not a depression. If you have a great product that somehow never seems to get the attention that it deserves, it may be that by creating a landing page and a campaign to drive traffic to that page is all you require, rather than a full website build and optimisation of every page on the website. To make sure that you are getting the best techniques for your business, better make sure that you have chosen the best SEO Company for your business needs.

  38. Torben

    Hey,
    thanks for sharing this. I tried it and it works, but however I was wondering if this is still the easiest and best solution in WP 4.3.1? Do you know that? Thanks!

  39. Aakash

    Works like a charm. Thankyou so much :)

  40. Fabrice

    Hello, I was wondering if this would work with a shared taxonomy between several custom posts.
    For example, if I have a CPT called “project” and another called “portfolio” and I have a custom taxonomy called “color” with terms like “yellow”, “red”… will this work like this ?
    on domain.com/projects/color/red I will have only projects that have the red color and on domain.com/porfolio/color/yellow only the yellow portfolio or will i have the yellow projects and yellow portfolio mixed ?

Leave a Reply

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