In Custom Post Type Permalinks – Part 1 we considered how to set different permalink options from the register_post_type function call. Here, we focus on how to create our own permalink structures.
With the register_post_type function, we can easily set our custom post type permalink structure to look like this (assuming custom post type = gallery).
However, we may want to fully customize our custom object permalink structure, similar to how we can fully customize our post permalink structure. Suppose we want to set our permalink structures as follows –
Post permalink structure
Gallery custom post type permalink structure
1. Turn Off Default Rewrite Rules
First, we set the rewrite argument to false in our register_post_type function call.
$args = array( 'publicly_queryable' => true, 'query_var' => true, 'rewrite' => false, ... ); register_post_type('gallery',$args);
After we set the argument to false, our gallery custom post type will no longer use pretty permalinks. Instead, our links will look like this –
http://shibashake.com/wordpress-theme?gallery=test-1
Note – It is also necessary to set query_var to true to enable proper custom post type queries.
2. Add New Custom Post Type Rewrite Rules
To get back our pretty permalinks, we need to add our own %gallery% rewrite tag, and our own gallery perma-structure.
// add to our plugin init function global $wp_rewrite; $gallery_structure = '/galleries/%year%/%monthnum%/%gallery%'; $wp_rewrite->add_rewrite_tag("%gallery%", '([^/]+)', "gallery="); $wp_rewrite->add_permastruct('gallery', $gallery_structure, false);
The add_rewrite_tag function accepts 3 arguments.
- tag name – Our custom post type tag name. E.g. %gallery%.
- regex – A regular expression that defines how to match our custom post type name.
- query -The query variable name to use for our custom post type plus an = at the end. The result of our regular expression above gets appended to the end of our query.
For example, suppose our pretty permalink is –
http://shibashake.com/wordpress-theme/galleries/2010/06/test-1
The result of the regular expression match from %gallery% is test-1. This value gets passed on as a public query to our main blog –
http://shibashake.com/wordpress-theme?gallery=test-1
Later, the query link gets translated back into our original pretty permalink so that our end-users are shielded from this whole process.
The add_permastruct function takes in 4 arguments.
- name – Name of our custom post type. E.g. gallery.
- struct – Our custom post type permalink structure. E.g.
/galleries/%year%/%monthnum%/%gallery%
- with_front – Whether to prepend our blog permalink structure in front of our custom post type permalinks. If we set with_front to true here, our gallery permalinks would look like this –
- ep_mask – Sets the ep_mask for our custom post type.
http://shibashake.com/wordpress-theme/articles/galleries/2010/06/test-1
This is not what we want, so we set it to false.
Adding the add_permastruct function changes our gallery object permalinks from
http://shibashake.com/wordpress-theme?gallery=test-1
to
http://shibashake.com/wordpress-theme/galleries/%year%/%monthnum%/test-1
which results in a 404 or file not found page error. This is because the permalink tags %year% and %monthnum% were not properly translated.
3. Translate Custom Post Type Permalink Tags
Finally we need to translate the additional tags in our custom post type permalink. To do this, we hook into the post_type_link filter. The code used in our tag translation function was adapted from the regular WordPress post get_permalink function.
// Add filter to plugin init function add_filter('post_type_link', 'gallery_permalink', 10, 3); // Adapted from get_permalink function in wp-includes/link-template.php function gallery_permalink($permalink, $post_id, $leavename) { $post = get_post($post_id); $rewritecode = array( '%year%', '%monthnum%', '%day%', '%hour%', '%minute%', '%second%', $leavename? '' : '%postname%', '%post_id%', '%category%', '%author%', $leavename? '' : '%pagename%', ); if ( '' != $permalink && !in_array($post->post_status, array('draft', 'pending', 'auto-draft')) ) { $unixtime = strtotime($post->post_date); $category = ''; if ( strpos($permalink, '%category%') !== false ) { $cats = get_the_category($post->ID); if ( $cats ) { usort($cats, '_usort_terms_by_ID'); // order by ID $category = $cats[0]->slug; if ( $parent = $cats[0]->parent ) $category = get_category_parents($parent, false, '/', true) . $category; } // show default category in permalinks, without // having to assign it explicitly if ( empty($category) ) { $default_category = get_category( get_option( 'default_category' ) ); $category = is_wp_error( $default_category ) ? '' : $default_category->slug; } } $author = ''; if ( strpos($permalink, '%author%') !== false ) { $authordata = get_userdata($post->post_author); $author = $authordata->user_nicename; } $date = explode(" ",date('Y m d H i s', $unixtime)); $rewritereplace = array( $date[0], $date[1], $date[2], $date[3], $date[4], $date[5], $post->post_name, $post->ID, $category, $author, $post->post_name, ); $permalink = str_replace($rewritecode, $rewritereplace, $permalink); } else { // if they're not using the fancy permalink option } return $permalink; }
We Are Done
Once we translate the additional permalink tags, our gallery permalinks will look like this –
http://shibashake.com/wordpress-theme/galleries/2010/06/test-1
And just like that – we are done!
Permalink Conflicts
A common issue that arises when you create your own permalinks are permalink conflicts.
Permalink conflicts occur when two permalinks share the same regular expression structure.
Note – it is regular expression structure and NOT tag structure.
You can use tags with different and unique sounding names but it will not remove your conflict issue as long as your regular expression structure remains the same.
When permalink conflicts happen, you will usually get a 404 Page Not Found error. This happens because when the WordPress system goes to look for the proper permalink rule to fire, there are multiple ones that match. As a result, the system will only fire the first rule that it sees. Those objects that are tied to all subsequent but duplicate patterns will necessarily return a Page Not Found error because the system is using the wrong permalink rule.
The easiest way to avoid permalink conflict issues is to insert a unique slug into your structure. For example, instead of doing –
/%author%/%gallery%/
Do –
/gallery/%author%/%gallery%/
Adding the unique slug/keyword gallery into your permalink structure ensures that there are no regular expression pattern conflicts.
Is it possible to have multiple objects share the same permalink rule?
Yes, but this can get very messy. One way to do this is to only create a single permalink structure for all of your target objects. Then you must manually resolve which object you want by hooking into the request filter hook.
homem robô says
Hi Shibashake, I need your help. I’m trying to do this structure:
mysite.com/%genre%/cases/%cases%/
mysite.com/%page%/%subpage%/
Cases is my custom post type.
Genre is my custom taxonomy.
But both genres and pages has the same slug, like, “literature”.
Example:
mysite.com/literature/ --> this is a page
mysite.com/literature/authors -> this is a page, child of literature
mysite.com/literature/case --> this is a list of cases with custom taxonomy "literature"
mysite.com/literature/cases/ray-bradbury --> this is a custom post type
Its possible? When I got the CPT working, all my pages get 404. When I get my pages working, all my CPT get 404…
If you have some suggestion on this subject, please.
Thanks!
ShibaShake says
Likely, this is a result of permalink conflict issues.
http://shibashake.com/wordpress-theme/custom-post-type-permalinks-part-2#conflict
All the listed regular expression structures are very similar, which causes the wrong permalink rules to fire. It is possible to have multiple object types share the same permalink rule, but that can get pretty messy.
Dan says
thanks for a great article. I’m still having problem wrapping my head around permalinks. I’m looking for a hierarchical permalink structure for my custom post type using custom taxonomies the same way you can do it with categories. Is there an article you know about that does this or should I have enough info here to do it myself? Thanks!
ShibaShake says
Hello Dan,
I do not know of any tutorials that does exactly what you say. Here is a tutorial on adding regular taxonomies into the permalink structure.
http://shibashake.com/wordpress-theme/add-custom-taxonomy-tags-to-your-wordpress-permalinks
You can replace your taxonomy tag with anything you want and probably create your hierarchy that way. The only thing to look out for are permalink conflicts.
evan says
Hi Shibashake,
I should first thank you for all the help you gave me while I was building this site. I should have sent you a link as soon as it went live.
I have another rewrite question which will probably be a snap for you and add to this already wonderfully comprehensive post.
The question: How do you display custom taxonomy per term archives.
The example is that I have a taxonomy: venues, and I want to be able to click on a venue and display all the posts with that term. For a specific example if you go to http://metroland.net/events/2011/03/16/sick-puppies-adelitas-way/ you will be able to click on the venue, but its currently giving a 404.
Again thanks for the help before and I hope you enjoy seeing the product of your great teaching.
ShibaShake says
Hello Evan,
Congratulations on your site! I am glad the article was helpful.
In terms of generating archived taxonomy pages one possibility is to hook into the request filter. I used that filter in my blog-art site to generate custom-post type gallery pages rather than regular post pages. Perhaps a similar process can be used to do what you want.
This article describes how I hooked into the request filter –
http://shibashake.com/wordpress-theme/mastering-the-wordpress-loop
Aram says
Thanks for this guide, I managed to get my permalinks correctly working with custom post types and categories, but not for custom taxonomies. For some reason, even when I successfully replace the %taxonomy% tag, adding a single ‘/’ into this will cause 404, presumably because of the regex. One thing I am curious about is why the regex rewrite causes conflicts. Since the regex ‘([^/]+)’ will return the last captured block in the URL not containing a ‘/’, does this mean that even for regular post types this regex will also attempt to match the last block and then pass it as gallery=something if we do not add a unique prefix? If I had the regular post structure as ‘/%year%/%monthnum%/%postname%’ and also:
$gallery_structure = ‘/%year%/%monthnum%/%gallery%’;
$wp_rewrite->add_rewrite_tag(“%gallery%”, ‘([^/]+)’, “gallery=”);
$wp_rewrite->add_permastruct(‘gallery’, $gallery_structure, false);
Then what happens? Does it change the permalink tags first with gallery_permalink() and then try to apply the list of regexes it has? So then it won’t be able to distinguish between a regular post and a gallery simply by the name? And so by adding ‘/gallery/’ in front of the the structure add_permastruct() will ensure that all galleries are distinguished behind the scenes, allowing it to make some checks before applying a regex?
Thanks!
ShibaShake says
WP matches the regular expression for the entire permalink. All the rewrite rules are stored in an associative array the ‘rewrite_rules’ option. Each rule looks like this –
The first part contains the regular expression and the second part contains the corresponding query. When a pretty premalink comes in, it is matched with each of these rules, and the first one to match gets translated into a query. If your gallery and post contains the same regular expression structure, then only the first rule that matches will be used.
The Frosty says
So this works much better than my class, but doesn’t like WordPress 3.1 and the new
has_archive
inregister_post_type
.It’s rewriting my base URL of /snippets/ to /2011/02/snippets/. Hmm.
ShibaShake says
Sounds like some of the old rewrite rules may still be around.
Try flushing the rules, and start with no added rewrite rules. Then slowly add them in one by one.
A quick way to flush rewrite rules is to go into Settings >> Permalinks and do a Save.
The Frosty says
I have to thank you for this. I’ve been using a class to do the same thing, but had issues with permalinks showing proper on the front end. May take a look and compare code bases.
josemv says
Hi there, I am using a plugin for managing datasets which sets permalinks as http://www.mysite.com/mypage/show=7 Can you point me out how can I change this link structure to pretty permalinks ? Thanx
ShibaShake says
Do you have permalinks set in Settings >> Permalinks?
If so, then it is likely that the plugin itself does not use pretty permalinks. In this case, it is best to contact the plugin creator, or you can edit the plugin and try to add it in yourself using the instructions outlined above (if the plugin uses custom post types).
Leo Plaw says
Thank you very much for this article. Yours is by far the best explanation of how to add custom permalinks to custom post types. It has been very helpful. 8)
Fab says
Hi,
i tested your code and work for me.
I have only a problem with 1 custom_post that i want with this structure http://mysite.com/%postname%/ (the same permalink that i use for native ‘post’ post_type) but not work. Maybe is a conflict.
I tried your code changing:
$gallery_structure = '/galleries/%year%/%monthnum%/%gallery%'
with this
$gallery_structure = '%gallery%';
but not works…
If i put something before like this
$gallery_structure = '/foo/%gallery%';
or this
$gallery_structure = '/%year%/%gallery%';
it works..
The only type of permalink that i cant get is this
$gallery_structure = '/%gallery%';
Do you now if it’s possible to get it work?
Thanks
ShibaShake says
This is because of conflicts in the regular expression structure –
http://shibashake.com/wordpress-theme/custom-post-type-permalinks-part-2#conflict
To get the same structure for multiple different objects, it is possible to hook into the request hook and do the conflict resolution manually, but it can quickly become messy.
Mark says
Actually, you need to straighten out that entire gallery_permalinks() function, as it is not correctly written – vars are mixed up.
ShibaShake says
Hmmm, I don’t detect any errors with the variable names.