A permalink or permanent link is a way for WordPress blogs to simulate static URL addresses. Since all blog data is stored in a database, and content is generated dynamically, blogs do not have static urls in the way that traditional websites do.
However, static URL addresses are still very desirable. They allow external sites to link to your blog, and are usually structured to be more human readable, and search engine friendly.
Here we examine how WordPress blogs deal with permalinks and how to add your own permalink structures.
How Permalinks Work in WordPress
- First, we request a pretty URL from our WordPress blog, for example, the url of this page.
- When this happens, WordPress translates the pretty URL into a generic index.php URL, together with relevant query arguments. For example, our pretty URL above will get translated into –
- The query arguments above are then used to query for the right objects from the WordPress database. Specifically, a query_posts call is made with query argument name = “wordpress-permalink-add”.
- The results from the query are then stored in the $wp_query global variable and are used to power all the WordPress Loop functions that get called by your WordPress theme.
- Finally the modified link
http://www.shibashake.com/wordpress-theme/wordpress-permalink-add
http://www.shibashake.com/wordpress-theme/index.php?name=wordpress-permalink-add
This occurs as part of the wp() function call in the wp-blog-header.php file.
http://www.shibashake.com/wordpress-theme/index.php?name=wordpress-permalink-add
gets converted back into our pretty permalink structure
http://www.shibashake.com/wordpress-theme/wordpress-permalink-add
so that our readers get shielded from this entire process.
The conversion back to our pretty permalink is achieved through the redirect_canonical function [wp-includes/canonical.php] which is tied to the template_redirect hook.
How to Add Your Own WordPress Permalinks
Recently, I wanted to add permalinks to the gallery objects in my Media Library plugin. Here are the steps –
- Translate gallery objects into the new gallery permalink structure.
- Add additional rules to our WordPress blog so that it can translate our gallery permalinks into a proper query, and then back again.
Suppose our WordPress blog permalink structure is
/articles/%postname%
and our desired gallery permalink structure is
/gallery/%gallery%
1. Translate gallery objects into our new gallery permalink structure.
We translate permalinks from our original blog structure into our gallery structure by hooking into the get_permalink function [wp-includes/link-template.php]. As of WordPress 2.9.2, we can use the post_link hook as is shown in the code below –
// Add filter to 'init' hook add_filter('post_link', array(&$this,'gallery_permalink'), 10, 3); function gallery_permalink($permalink, $post, $leavename) { if ($post->post_type == 'gallery') { $permalink_structure = get_option('permalink_structure'); // Assign each permalink component to their respective permalink placeholders // trim beginning and end slashes $perma_placeholders = explode('/', trim($permalink_structure, '/')); $perma_segments = explode('/', trim(str_replace(get_bloginfo('url'),"",$permalink), '/') ); // Create associative array between placeholders and segments $perma_array = array(); for ($i = 0; $i < count($perma_placeholders); $i++) { if ($perma_placeholders[$i]) $perma_array[$perma_placeholders[$i]] = $perma_segments[$i]; } $perma_array['%gallery%'] = $post->post_name; $new_permalink = get_option('gallery_structure'); if (!is_array($perma_array) || (count($perma_array) <= 0) || !$new_permalink) return $permalink; foreach ($perma_array as $key => $value) { $new_permalink = str_replace($key, $value, $new_permalink); } return get_bloginfo('url') . $new_permalink; } return $permalink; }
Line 6 – We only modify permalinks for gallery objects.
Line 7 – We retrieve our gallery permalink structure, i.e.
/gallery/%gallery%
which we assume is stored in the ‘gallery_structure’ option.
Lines 9-19 – We identify which values in our pretty URL corresponds to which permalink tag. Some example permalink tags include %year%, %tag%, %category%, or %postname%.
If our blog permalink structure is
/articles/%postname%
our page URL will look like this –
http://www.shibashake.com/wordpress-theme/articles/wordpress-permalink-add
In this case, we only have a single tag (%postname%), which the above process will associate with wordpress-permalink-add.
Line 20 – We set our %gallery% tag to contain the same value as the %postname% tag.
Lines 20-26 – We create our gallery URL by starting with the gallery permalink structure
/gallery/%gallery%
and replacing each of the tags with the values obtained above.
In particular, we replace %gallery% with wordpress-permalink-add so that our new gallery permalink looks like –
http://www.shibashake.com/wordpress-theme/gallery/wordpress-permalink-add
Since we can only hook into the end of the get_permalink function call, our hook function is needlessly complex and more limited.
What we really want is a hook at the beginning of the get_permalink function so that we may replace our regular blog permalink structure with our gallery structure. Then, all we need to do is pass our gallery structure into the get_permalink function.
The good news is that this pre_post_link hook should be included in the next WordPress release (WordPress 3.0).
Once that happens, our permalink conversion code will look like this –
add_filter('pre_post_link', array(&$this,'pre_gallery_permalink'), 10, 3); function pre_gallery_permalink($permalink, $post, $leavename) { if ($post->post_type == 'gallery') { $permalink = get_option('gallery_permastruct'); } return $permalink; }
2. Add rewrite rules to our WordPress blog to translate gallery permalinks
Now, we are done with converting our gallery permalinks.
Next, we must add some rules into our WordPress blog so that it knows how to convert our new permalinks into an index.php query and vice versa.
We do this through the WordPress WP_Rewrite class [wp-includes/rewrite.php].
First, we create a new permalink tag for our gallery objects.
// Add to the init hook function global $wp_rewrite; $wp_rewrite->add_rewrite_tag("%gallery%", '([^/]+)', "post_type=gallery&name=");
In this way when the WP_Rewrite object sees a permalink structure that contains the %gallery% tag, it knows how to generate the proper conversion rules.
function set_gallery_permastruct() { global $wp_rewrite; $gallery_structure = get_option('gallery_structure'); if ( $gallery_structure ) { // false is important in add_permastruct so that it does not append the general blog structure our gallery structure $wp_rewrite->add_permastruct('gallery', $gallery_structure, false); // write permastruct for attachments [...] } else { $wp_rewrite->add_permastruct('gallery', "", false); } }
Line 7 – Add conversion rules for our gallery permalink structure using the add_permastruct function.
The third argument (boolean) specifies whether we want to prepend our general blog permalink structure onto our gallery permalinks. If we specify it to be true, the gallery structure used to generate our rules will look like this –
/articles/gallery/%gallery%
This is not the result we want, thus, we set the third argument to false.
Line 8 – We may want to generate additional rules for attachments that are linked to galleries.
Attachments that are linked to posts or galleries may include its parent object URL in its permalink. For example, suppose the attachment test_1 is linked to our wordpress-permalink-add gallery. In this case, the test_1 attachment will have a permalink that looks like this –
http://www.shibashake.com/wordpress-theme/gallery/wordpress-permalink-add/test_1
There are multiple ways to deal with these attachments –
- Add conversion rules for these new attachment permalinks.
- Alter attachment permalinks (using the attachment_link hook) so that parent URLs are not included.
http://www.shibashake.com/wordpress-theme/?attachment_id=123
http://www.shibashake.com/wordpress-theme/articles/wordpress-permalink-add/test_1
Finally, we just need to add our new permalink rules into our WordPress blog initialization process. Our init hook function will now look something like this –
function init_general() { global $wp_rewrite; $wp_rewrite->add_rewrite_tag("%gallery%", '([^/]+)', "post_type=gallery&name="); $this->set_gallery_permastruct(); }
Clean Up
We are almost all done. The only thing left to do is to clean up after ourselves.
It is important to flush/remove all the rewrite rules added by our plugin when it is deactivated. This allows users to cleanly revert back to their previous state if there are bugs in our plugin rewrite rules.
// register deactivation hook during plugin object creation register_deactivation_hook( __FILE__, array(&$this,'deactivate' ) ); function deactivate() { global $wp_rewrite; $wp_rewrite->flush_rules(); }
In the rewrite.php file, it is recommended that we flush our rewrite rules on both plugin activation and deactivation so that we also clean out state that may have been left by previous plugins.
I should also mention that some of this permalink functionality will be automatically added by the register_post_type function in WordPress 3.0.
At this point, however, it is unclear how much control you will have over the permalink structure when using this command.
For a working example of the permalink code described above, check out the Shiba Media Library plugin.
nate Love says
How do I get the path: domain.com/watch?v=id encode Thanks
Javier says
Hope you can help
Currently I have this structure in WordPress
/% category% /% postname% /
I want to change the permalink of the pots in one category, and leave the rest the same.
These posts should have this permalink:
/% post_id% /% category% /% postname% /
Could you help me how to do it. I tried with your article do but I don’t get it.
Thank you so much
Mark says
I’ve read three or four of your post and I’m still stumped. I’m trying to do a permalink “reorg”. I thought it was as simple as using add_rewrite_rule but that doesn’t seem to be working. Perhaps you can help? Let me explain.
The current site has a number of customer post types. The URLs look like: mysite.com/post_type/post-name-URL-slug.
What we want to do is add two tracking segments. Think breadcrumbs, in a way. For example
mysite.com/page/some-page-name/post_type/post-name-URL-slug
mysite.com/page/some-other-page-name/post_type/post-name-URL-slug
Send end content post but coming at it from two different directions. It’s an analytcis related issue, if you’re wondering.
I’ve added a rewrite rule to parse the new URL but when I do the request, the added “tracking” segments get removed. I get the content I want (good!) but the URI changes (not good).
What am I overlooking here?
ShibaShake says
My guess is that a different permalink rule is firing instead of the one you want. Given the structures you posted, there may be conflicts with existing permalink rules.
http://shibashake.com/wordpress-theme/custom-post-type-permalinks-part-2#conflict
Alessio says
Is possible create an alias for permalinks of post? if I add a post with poermalink /blog/category/post.html I can add an alias permalink like /blog/category/post/more.html ?
this 2 permalinks must get same post id.
ShibaShake says
Sounds like using server redirects would be the better way to go.
But if you want to do this within WordPress, you can just hook into the request filter hook, check for your more.html name within your function, and then redirect it to the proper post id in the returned query arguments.
Daniel Iser says
Great articles on custom permalinks. for the site im working on i want links like this
/products/post-type/tax-term/tax-term-child/post-name/
i have been able to get the link structure correct using the post_type_link filter and a foreach() loop on get_the_terms() for that post.
my cpt is registered with slug of ‘products/residential/%res_brands%’
here is that code
`function product_permalink($permalink, $post_id, $leavename) {
if (strpos($permalink, ‘%res_brands%’) === FALSE) return $permalink;
// Get post
$post = get_post($post_id);
if (!$post) return $permalink;
// Get taxonomy terms
$terms = wp_get_object_terms($post->ID, ‘resbrands’);
if (!is_wp_error($terms) && !empty($terms) && is_object($terms[0])) {
$term_count = count($terms);
$i=1;
if($term_count > 1){
foreach( $terms as $term ) {
if (!empty($term) && is_object($term)) $taxonomy_slug .= $term->slug;
if($i slug;
}
}
//else $taxonomy_slug = ‘not-rated’;
return str_replace(‘%res_brands%’, $taxonomy_slug, $permalink);
}`
I am running into trouble coming up with the proper wr_rewrite rules. I have tried several things. Im assuming that because my %res_brands% could be several tags with / is the problem.
Also i am trying to rewrite my function similar to the one used in this article but any help would be great.
Daniel Iser says
Not sure but assuming that you only need the last child in the category, the lowest sub can i break it apart to use only the last slug for the wp_rewrite?
because %res_brands% could be parent/child/grandchild and posts assigned to the grandchild category would show up under parent and child so for the rewrite rule im assuming i need to change ‘([^/]+)’ to something that selects only the text after the last ‘/’?
$wp_rewrite->add_rewrite_tag(“%res_brands%”, ‘([^/]+)’, “resbrand=”);
ShibaShake says
Hello Daniel,
I’m afraid I won’t be of much help here. Regular expressions are really not my forte. I usually just bang at it, try out a whole bunch of different patterns, bang at it some more, until something works. 😀
ShibaShake says
Sorry I misunderstood you.
If you want to add %cat_id% to your general permalink structure, i.e. to your post links, then you want to use the post_link hook.
You just need to replace the %cat_id% tag with the proper id. The function would look a lot simpler because you wouldn’t need to construct the whole permalink structure from scratch as I did with the gallery objects above.
Instead your permalink function would just be something like –
Rewrite tag would be something like this –
Anyway, this is the general idea – you may need to change some things to get it exactly right.
Ryan says
This will be very helpful for what I need to do. I need to have the ability to add the category id to the permalink structure. So, http://www.mydomain.com/%cat_id%. To get started, would I use the category_link filter?
ShibaShake says
Yeah the category_link filter will allow you to alter the category permalinks to suit your needs but you must also add in proper rewrite rules afterwards.
The most challenging part is making sure that your new rewrite rules do not mess up the existing rewrite rules – i.e., you don’t misfire a category rule when you are actually looking for a post. For example htt://mydomain.com/%cat_id% may conflict with htt://mydomain.com/%post_id%
Ryan says
The full permalink structure that I want to end up having is as follows:
mydomain.com/%cat_id%/%post_id%/%category%/%postname%
This way I can use only: mydomain.com/%cat_id%/%post_id% for posting to twitter and the like. While also getting the seo benefits of the longer permalink structure that includes the category and postname.
Now, how would you suggest that I do my conditional in my function? Would something like this be a step in the right direction?
if ($term->taxonomy == ‘category’){
……
//then I would set the $perma_array as follows:
$cat = get_the_category();
$perma_array[‘%cat_id%’] = $cat->cat_ID;
……..
}