Understanding Rewrite Tags, or, Custom Permalinks for Custom Post Types in WordPress 3.0+, Part 2

You may recall my ancient post about customizing your custom post type permalinks. When I wrote that, I was still stumbling somewhat blindly through WordPress’s rewrite API. After hacking a little more with it, I think I finally understand what I’m doing enough to write a little more confidently.

Rewrite Tags

In my previous post, I had a slug like project/%issue_project%/issue for my issue post type. What exactly is that %issue_project% in the middle of that?

That would be a rewrite tag. WordPress uses these as keys for regular expressions it will add to its rewrite rules. When you add a custom post type or a custom taxonomy, WordPress automatically creates appropriate rewrite tags for you. E.g., when you create an issue post type, WordPress will automatically create a rewrite tag called %issue%, with the pattern ([^/]+), or (.+?) if it’s a hierarchical post type, and this regexp will be rewritten to post_type=issue&name=$matches[1].

In my previous post, %issue_project% was the tag for the rewrite rule matching the issue_project taxonomy I had created using register_taxonomy.

Making Your Own Rewrite Tags

Knowing all this, you can create your own rewrite tags and use them in the slugs for your custom post types.

For my issue/bug tracking plugin, I decided that I should use another custom post type instead of taxonomy for projects. That means that WordPress won’t automatically create the %issue_project% rewrite tag. I don’t want to use the %project% rewrite tag that it creates for my project post type, as that will add inappropriate (and conflicting) rules to my rewrite rules.

The solution is to create my own %issue_project% rewrite tag.

global $wp_rewrite;
$wp_rewrite->add_rewrite_tag('%issue_project%', '(.+?/)?', 'issue_project=');

This matches (.+?/)? in a URL and puts the match into an ignored query var. Having created the rewrite tag, I can now use it in my post type’s slug.

register_post_type('issue', array(
  'hierarchical' => FALSE,
  'rewrite' => array(
    'slug' => '%issue_project%issue',
    'with_front' => FALSE,

This creates rewrite rules like

[(.+?/)?issue/([^/]+)(/[0-9]+)?/?$] => index.php?issue_project=$matches[1]&issue=$matches[2]&page=$matches[3]

Creating Links

Whenever you create your own rewrite tags, or use them in non-standard ways in post type slugs, you have to handle the rewrite tag in a post_type_link filter, replacing it with the appropriate string.

add_filter('post_type_link', 'my_post_type_link'), 1, 3);
function my_post_type_link( $post_link, $post = 0, $leavename = FALSE ) {
  if ( strpos('%issue_project%', $post_link) === 'FALSE' ) {
    return $post_link;
  if ( is_object($post) ) {
    $post_id = $post->ID;
  } else {
    $post_id = $post;
    $post = get_post($post_id);
  if ( !is_object($post) || $post->post_type != 'issue' ) {
    return $post_link;
  $project_slug = '';
  $project_id = get_post_meta($post_id, '_issue_project', TRUE);
  if ( $project_id ) {
    $project_slug = get_page_uri($project_id);
  if ( !$project_slug ) { // remove project prefix
    return str_replace('%issue_project%', '', $post_link);
  // put project slug in place of %issue_project%
  return str_replace('%issue_project%', 'project/'.$project_slug.'/', $post_link);

4 thoughts on “Understanding Rewrite Tags, or, Custom Permalinks for Custom Post Types in WordPress 3.0+, Part 2”

  1. So what if you want your custom post type to not have a prefix slug? For example:

    If you create a custom post type “people” and the permalinks naturally go to example.com/people/slug-of-people-post


    you want them to be example.com/slug-of-people-post so that they are a first level slug in the url almost just appearing as if they are a page?

  2. Hello,

    Using your solution, is it possible to have a structure like this without getting 404 :
    custom post type (= product1) is set to have a custom taxonomy (=subcat-customtax) which in turn is a child of the parent category (=customtax). Is it possible that the url
    http://site.com/customtax/subcat-customtax/product1/ to return the above mentioned post correctly ??

    I currently can see all the posts within subcat-customtax, the url being site.com/customtax/subcat-customtax/
    All the products have just the assigned category, not the full structure (parent/child tax) and even so, upon accessing i get a 404.

Comments are closed.