The WordPress custom taxonomy system allows you to create your own grouping of WordPress objects (e.g. posts, pages, and custom post types). Tags and categories, for example, are taxonomy objects that are native to the WordPress system. With the custom taxonomy framework, you can create your own tags and categories.
In the article Custom Post Type Permalinks (Part 2), Lane and Andrew asked the very good question of how we can add custom taxonomy tags to WordPress permalinks.
For example, if we have a custom taxonomy called rating we might want to set our WordPress permalink structure to –
/%rating%/%postname%
Here we consider how to insert custom taxonomy tags into the WordPress permalink structure.
1. Create a Custom Taxonomy
First, we create a custom taxonomy object called rating with the register_taxonomy WordPress function.
add_action('init', 'my_rating_init'); function my_rating_init() { if (!is_taxonomy('rating')) { register_taxonomy( 'rating', 'post', array( 'hierarchical' => FALSE, 'label' => __('Rating'), 'public' => TRUE, 'show_ui' => TRUE, 'query_var' => 'rating', 'rewrite' => true ) ); } }
Setting the rewrite argument to true (Line 10) will automatically add the tag %rating% to our WordPress system.
However, if we try to set our blog permalink structure to –
/%rating%/%postname%
Our new post permalinks will look like this
http://shibashake.com/wordpress-theme/%rating%/test-1
As a result we will get a 404 file not found error. This is because our %rating% tag was not properly translated.
2. Translate Our Custom Taxonomy Tag
To translate our new %rating% tag, we must hook into the permalink generation function – get_permalink.
The post_link hook allows us to translate tags for regular post objects and the post_type_link hook allows us to translate tags for custom post type objects.
Note that both filter hooks accept exactly the same arguments, so we can tie both hooks to the same function.
add_filter('post_link', 'rating_permalink', 10, 3); add_filter('post_type_link', 'rating_permalink', 10, 3); function rating_permalink($permalink, $post_id, $leavename) { if (strpos($permalink, '%rating%') === FALSE) return $permalink; // Get post $post = get_post($post_id); if (!$post) return $permalink; // Get taxonomy terms $terms = wp_get_object_terms($post->ID, 'rating'); if (!is_wp_error($terms) && !empty($terms) && is_object($terms[0])) $taxonomy_slug = $terms[0]->slug; else $taxonomy_slug = 'not-rated'; return str_replace('%rating%', $taxonomy_slug, $permalink); }
Line 5 – If the permalink does not contain the %rating% tag, then we don’t need to translate anything.
Line 12 – Get the rating terms related to the current post object.
Line 13 – Retrieve the slug value of the first rating custom taxonomy object linked to the current post.
Line 14 – If no rating terms are retrieved, then replace our rating tag with the value not-rated.
Line 16 – Replace the %rating% tag with our custom taxonomy slug.
Our new post permalinks will look like this –
http://shibashake.com/wordpress-theme/rated-a/test-1
Paul says
Brilliant! Just what I was looking for. Nice work on all the wordpress customizations, much appreciated.
robert felty says
Thanks for the great article. I was wondering if there is some way to do this for categories too. I am building a site which has the same categories for several different cities. I have created a custom taxonomy called city, and have successfully gotten posts to use the structure:
%city%/%postname%
I would also like this for categories, so I could have
%city%/%category%
For example,
domain.com/new-york/food/fine-dining
would return all posts categorized as ‘fine-dining’ that also have the city term=’new-york’ (and fine-dining is a subcategory of food).
Thanks in advance for any advice.
ShibaShake says
Hello Robert,
I haven’t done this before with categories, but it should definitely be very doable.
You can probably replace the existing permalink structure of categories with $wp_rewrite->add_permastruct. Something like this –
$wp_rewrite->add_permastruct(‘category’, $category_structure, false);
Here is where taxonomy permalink rules get added in the WordPress source –
http://phpxref.ftwr.co.uk/wordpress/nav.html?wp-includes/taxonomy.php.source.html#l325
Dave says
Great article! In fact, you have lots of great articles on WordPress.
I followed your method and my custom permalinks for several custom post types are working great. they replace %artist% in my URL with an artist name. However, whenever I try to view a page I get a ‘page not found’ error.
Also, same thing happens for normal posts. I can get posts working if I set my permalink settings to /%artist%/%postname% but if I leave out %artist% I get a ‘post not found’ error.
Any ideas where the conflict is occurring for pages and posts?
Cheers
ShibaShake says
Hello Dave,
What you describe sounds like the dreaded permalink conflict issue. I have added a section to the main permalink article that explains this problem –
http://shibashake.com/wordpress-theme/custom-post-type-permalinks-part-2#conflict
Dave says
It seems that permalink conflict issue was the culprit.
I added a unique name to the beginning of each custom post type permalink structure (as advised by your article) and I can now view posts (normal and custom) and pages without any problems.
Cheers for the help.
Keep up the good work, your site is the best resource for tricky WordPress issues!
Frederic says
Hi there,
There is a small typo in the following code:
`add_filter(‘post_type_link’, ‘rating_permalink’), 10, 3);`
Should be:
`add_filter(‘post_type_link’, ‘rating_permalink’, 10, 3);`
I removed a parenthesis.
ShibaShake says
I have corrected it. Thanks for letting me know. 😀
Mauko Quiroga says
I’m having an issue here too. I have adapted your code for my two taxonomies: ‘artist’ and ‘writer’. Also, I use a custom post type ‘story’.
When I define the rules to /%artist%/%writer%/%postname% or /%artist%/%writer%/%story% I get a not found.
ShibaShake says
In general, I find that it is best to go slowly –
1. First, make sure the custom post type permalink is working, i.e. don’t include a taxonomy tag yet.
2. Then only include one taxonomy tag.
3. Once that works, add another one and so on.
At this point, it is difficult to tell whether the issue is with the custom post type or with the taxonomy. To debug, you can also hook into the ‘request’ filter and see what the query arguments are.
Homem Robô says
Hi there, first, nice work! I learn some cool tricks with your tutorials. But can you help me? I try almost 2 days to get this structure to working:
For custom post types:
/%category%/%child_category/%post_type%/%postname%/
/%category%/%post_type%/%postname%/
For normal posts:
/%category%/%child_category/%postname%/
/%category%/%postname%/
But no lucky. I’m using the default wordpres “categories” as taxonomies for posts and other post_types. But when I got the permalink right on admin, my template get 404.
Thanks,
homem robo
ShibaShake says
For custom post types you need to use the custom post type tag. For example, if my custom post type = gallery, then I do
/%category%/gallery/%gallery%/
If you want to use your own tags, e.g. %child_category% or %post_type% you must first define them. Permalink tags can get created during taxonomy or custom post type creation, or you can create them yourself using add_rewrite_tag.
Here is an article specific to setting the permalinks for custom post types –
http://shibashake.com/wordpress-theme/custom-post-type-permalinks-part-2
Zak says
I am designing a website for a newspaper. I used this tutorial to get the article permalinks to be http:…/article/[issue-date]/[section]/[article-headline]. It worked for a while but broke a few days later when I was working on something unrelated. I changed my code back but it still doesn’t work. It changes the permalinks correctly and the links lead to the correct place but I get a 404 page. Any ideas what could be wrong?
Zak says
Sorry for posting again but I forgot to add an observation I had made. I found it pretty odd that it could somehow get the correct article information but not find the article. I mean it needed the article in the first place to get the proper taxonomies, right?
ShibaShake says
My guess is that there is a conflict with some other permalink structure that you are using. If there is a conflict (i.e. two permalink rules that match the same address string), then only the first rule that matches will fire.
It could also be that the permalink rules weren’t properly flushed before. Every time you change any permalink structure you want to flush your permalink rules so that you are sure that old permalink structures are fully cleared.
Zak says
I flush the permalinks about every 30 sec (sometimes twice after I make changes just to make sure) and I don’t know what other permalink structure could be conflicting. I even restarted my computer multiple times since it happened and manually cleared out whatever caches I could find. It is not just a browser specific problem though.
I honestly don’t remember anymore what changes I made when I first encountered this problem but the first thing I did when it happened was to revert the changes. Upon researching I saw a lot of people mention the .htaccess file, could there maybe be conflicting rewrite rules there because I can’t think of anywhere else where I have played with rewrite rules.
I don’t think it is a conflicting name problem as the whole reason I created this long permalink structure was to avoid conflicts and also it happens with all articles…
ShibaShake says
Try linking into the ‘request’ filter and print out the query arguments when you access the 404 page.
For example –
This will help you to identify which rewrite rule got fired and why the page did not get retrieved.
Zak says
Thanks for the suggestion, I had been looking for some way to debug the request. I did it though and only got this output:
(condensed to one line for space) Not sure where do go from there. Does this mean that it is directly calling a 404 error or that the problem is happening before the call. The site is locally hosted so unfortunately I can’t provide an address to show you but if there is any code or settings that might help I would be happy to provide them. Thanks for the attempt though.
Oh and would it make any difference what my normal post rewrite rules are set to? I have /%postname%/%post_id% now, which works for normal posts and printed out the right info from the code above in a comment.
Zak says
Sorry for the number of comments but I just noticed that in the above comment the output was cut off. Basically I got an array with “[error] => 404” in it.
ShibaShake says
Hmmm, that seems to indicate that none of the rewrite rules fired. Is article-headline a custom post type? It could be an issue with the rewrite tag or an issue with the add_permastruct call.
At this point I would just try the taxonomy tags in your regular posts just to make sure that they work. Then I would test out your custom post type permalinks with the regular WP structure. Then test it out with your own structure but with no taxonomy tags. Once that is all working, slowly add in 1 taxonomy tag at a time.
Zak says
Well Article is the custom post type, article headline was what I was calling the title of the article. The odd thing is that it worked for a while so I know that it is possible to do. I will try adding the taxonomies back into regular posts and then seeing if it works with them.
I feel like although it seems to not be firing the rewrite rules, it also seems like it has to be doing it. It changes the strings everywhere. When I go to edit an article the permalink displayed at the top will have the changes. All the links have the changes.
I’ll keep looking through and try out your suggestions and post back whenever I have anything. Thanks.
Zak says
I just realized that I forgot to include an important (I think at least) detail. I mentioned that I am dealing with the article post type but the rewrite slug of article is set to “article%issue%%section%” with issue and section being replaced. I did it that way so that if for some reason an article does not have an issue or section it replaces it with “” and if it does it replaces it with “/{whatever}.” This worked for a while so I know the method isn’t what is messing up.
Edward B says
You saved me a lot of time! thanks a lot for this !
Josh Byers says
The taxonomy retrieval function is what I don’t understand.
Does it work the same as when you are retrieving taxonomy values on a single post page?
e.g.
ShibaShake says
Yeah you can do that. Now you just need to create a string out of the terms so that you may replace it into your permalink string at the %speaker-name% tag location. For example something like this –
Josh Byers says
Sorry I didn’t reply on the previous comment – feel free to move it if you want to keep the conversation context.
So this is what I have:
Do I need to specify any parameters in my custom post type as it relates to the rewrite value? With the above code I tried this with the rewrite value:
But the permalink comes back as: wp-admin/none
When I don’t add any rewrite parameter nothing changes in the permalink.
Any ideas?
Thanks for all the help
ShibaShake says
Also your podcast rewrite permalink should be something like
Josh Byers says
Wonderful! Works great with a couple exceptions.
First – when you hover over the view link when looking a list of posts in the admin it doesn’t fill in the taxonomy it defaults to the ‘else’ value.
Second – the same thing happens when you query the posts for display on the front end and declare that the ‘post_type’ => ‘podcast’. If you don’t specify the post_type in your query the permalink comes up fine.
I’m assume the admin is also querying the posts and specifying that the type is podcast, so the two issues are most likely related.
That’s not a big deal for me at this point but its definitely not ideal.
Anyway, thanks for the help. I really appreciate it.
ShibaShake says
That sounds strange. So in both cases it is showing up as /no-speakers/test-1?
Two things that come to mind –
1. Make sure to flush your rewrite rules.
2. Do some echos in your function and see what terms are being retrieved by wp_get_object_terms.
Josh Byers says
Yep. Whatever value is assigned to ‘else’ shows up in the permalink in those two instances.
I have flushed my rewrite rules by both visiting the permalinks page and by changing the default permalinks but neither helped.
When I echoed out the terms it retrieved the correct object and displayed it fine.
I then did some additional investigating and found another post on the subject: http://xplus3.net/2010/05/20/wp3-custom-post-type-permalinks/
Comparing your code and his it looks like you have to declare the post_type so when you query it brings back the correct object.
Doing that solved both issues!
Thanks for the help and teaching me something new.
Josh Byers says
Thanks for all the different articles on changing up the permalinks.
I’m a little fuzzy however trying to figure out how to enter my own function to retrieve a specific taxonomy value.
I have a custom taxonomy called ‘speaker-name’ with the values of different speakers.
I have a custom post type called ‘podcast’. I want the permalink to include the name of the speaker which is checked in the ‘speaker-name’ taxonomy.
Thanks!
ShibaShake says
You want to hook into the post_type_link filter then.
Then just pattern your podcast_permalink function similar to the rating_permalink example I showed above. In line 10 is where you want to put in your own taxonomy retrieval function. Then make sure to replace the %rating% tag with %speaker-name%.