by ShibaShake

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).

http://shibashake.com/wordpress-theme/gallery/test-1

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

/articles/%postname%

Gallery custom post type permalink structure

/galleries/%year%/%monthnum%/%gallery%

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.

  1. tag name – Our custom post type tag name. E.g. %gallery%.
  2. regex – A regular expression that defines how to match our custom post type name.
  3. 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.

  1. name – Name of our custom post type. E.g. gallery.
  2. struct – Our custom post type permalink structure. E.g.
    /galleries/%year%/%monthnum%/%gallery%
  3. 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 –
  4. http://shibashake.com/wordpress-theme/articles/galleries/2010/06/test-1

    This is not what we want, so we set it to false.

  5. ep_mask – Sets the ep_mask for our custom post type.

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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
// 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!

Related Articles

<< Previous Next >>

<a href="http://shibashake.com/wordpress-theme/custom-post-type-permalinks" target="_top">Custom Post Type Permalinks</a>

Custom Post Type Permalinks

Custom post types is a very powerful feature in WordPress 3.0. With custom post types you can easily create post-like objects and automatically have menus, metaboxes, icons, permalinks, and much more added without having to code any of it yourself. Here we consider custom post type permalinks - how to set it, what the different options mean, and...

<< Previous Next >>

<a href="http://shibashake.com/wordpress-theme/add-custom-taxonomy-tags-to-your-wordpress-permalinks" target="_top">Add Custom Taxonomy Tags to Your WordPress Permalinks</a>

Add Custom Taxonomy Tags to Your WordPress Permalinks

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...

<< Previous Next >>

<a href="http://shibashake.com/wordpress-theme/add-custom-post-type-columns" target="_top">Add Custom Post Type Columns</a>

Add Custom Post Type Columns

One of the exciting new features in WordPress 3.0 is custom post types. We can create our own post types by using the register_post_type function. If we enable UIs for our custom post type, we will get additional menu items on our WordPress dashboard similar to the Edit and Add New options for standard posts. Here, we consider how to add new columns...

<Playback Stop Play >

27 Comments

  1. Senhor W

    Nice post, now i have a custom post permalink.

    Thank you!

    1:16 pm on September 8th, 2010 Reply
  2. jason

    I’ve tried so many things to get permalinks working on a site with 3 custom post types. I can always seem to get things to work, but then normal pages (page.php) start giving the 404.

    So I’ve tried to just turn off the permalinks for the main custom post type (release) and use your tutorial…but once putting in the code (changing gallery to release) and putting into the theme’s functions.php file I get:

    Warning: call_user_func_array() [function.call-user-func-array]: First argument is expected to be a valid callback, ‘release_permalink’ was given in /home1/artistid/public_html/dtfcanvas/wp-includes/plugin.php on line 166

    when viewing the list of Release custom post types…..any ideas?

    Are you for hire to help me sort this out? The site is due to launch this week….it all works when permalinks are set to default, and I need to keep the structure /%year%/%monthnum%/%postname%/

    so I don’t loose hundreds of facebook likes.

    On another site I’m working on, everything works fine while using just /%postname%/ permalink. On my current site, /%postname%/ will let me see my custom posts, and page.php but then single.php gives me the 404.

    if I switch it to /%year%/%monthnum%/%postname%/ then single.php and my custom post permalinks work, but page.php goes to the 404….

    what in the world is going on!!!!

    2:55 pm on August 30th, 2010 Reply
    • Warning: call_user_func_array() [function.call-user-func-array]: First argument is expected to be a valid callback, ‘release_permalink’ was given in /home1/artistid/public_html/dtfcanvas/wp-includes/plugin.php on line 166

      This is likely because you specified release_permalink as the filter function but did not change the name of the function itself. It should look like this -

      add_filter('post_type_link', 'release_permalink', 10, 3);
      function release_permalink($permalink, $post_id, $leavename) {
      	$post = get_post($post_id);
      ...
      }

      Are you for hire to help me sort this out?

      Due to time constraints, I am unable to work on external projects.

      if I switch it to /%year%/%monthnum%/%postname%/ then single.php and my custom post permalinks work, but page.php goes to the 404….

      404 errors usually occur when there are conflicts in your permalink structures. For example, if all or some of your custom post types use permalink structures that are similar to existing WordPress object permalink structures. That is why it is usually safest to have a unique slug in your custom post type permalink structure, for example -

      /unique_slug/%release%

      Also make sure to flush your rewrite rules when you change permalink structures.

      // add to your plugin deactivation function
      global $wp_rewrite;
      $wp_rewrite-&gt;flush_rules();

      Here is what I usually do -
      First, turn off all your custom permalinks. Flush rewrite rules. Then make sure that post and page permalinks are working.

      Then just turn on one of your custom permalinks, and make sure it is unique. Make sure that post, page, tag, category, archive, comment page, and all other existing WP permalinks are still working. Then slowly add new permalink rules as necessary.

      Good luck.

      4:06 pm on August 30th, 2010 Reply
  3. [...] Custom Post Type Permalinks – Part 2 please tell me it's not really this much of a pain in the ass to add a date to custom post type permalinks. (tags: wordpress code) This entry was posted in Delicious. Bookmark the permalink. ← links for 2010-08-27 [...]

    2:03 am on August 28th, 2010 Reply
  4. So I’ve run into another custom post type rewrite riddle for you.

    The answer seems like it should be very similar to my original question.

    Using the same archive set up (http://shibashake.com/wordpress-theme/galleries/%year%/%monthnum%/%day%/) I want to be able to change the $query string to show posts listed by comment_count instead of post_date

    I looked at the wp_get_archives function, but I’m not sure how to apply a filter to a if statement like that.

    9:15 am on July 21st, 2010 Reply
    • You can just use the orderby argument -

      query_posts('post_type=gallery&orderby=comment_count');

      You can also add the orderby property in your previous ‘request’ filter function.

      2:26 pm on July 22nd, 2010 Reply
  5. Hey,

    Thanks for all these tutorials you’ve done on custom post types and custom taxonomies, I’ve found them absolutely invaluable.

    I’ve followed this tutorial through, and have created my permalinks how I want them, which is basically as per normal Posts, /%category%/%postname%/.

    These permalinks appear correctly on the edit screen, but visiting one results in a 404.

    I’ve put the code I’m using in a pastebin: http://wordpress.pastebin.com/0VUz0vCm

    Any ideas what is going wrong? I’ve visited the permalink setting page as some places tell you to do if you are getting a 404.

    Thanks in advance,

    Alex

    11:16 am on July 19th, 2010 Reply
    • Hi Alex,
      If I understand you correctly you want to set your custom post type permalink to be exactly the same as your post permalink? If that is the case, then you will have to take a different approach. The tutorial above only works if the permalink structures are unique.

      If you want two object types to have the same permalink structure, you will need to hook into the query request function (‘request’) filter and parse your new custom post type in there.

      It is doable but somewhat messy.

      $player_structure = '/%category%/%postname%/';
      $wp_rewrite->add_rewrite_tag("%category%", '([^/]+)', "category=");
      $wp_rewrite->add_permastruct('player_articles', $player_structure, false);

      Also, there is no need to add the %category% tag – the category tag is already added as part of WordPress native. You only need to add new tags – for example your new custom post type tag.

      $player_structure = '/player/%category%/%player_articles%/';
      $wp_rewrite->add_rewrite_tag("%player_articles%", '([^/]+)', "player_articles=");
      $wp_rewrite->add_permastruct('player_articles', $player_structure, false);

      I added /player/ to the front of the permalink structure so that it is unique. If two permalink structures are identical for two object types then the rewrite rules will be exactly the same and the system will just fire the first one that it sees.

      2:12 pm on July 19th, 2010 Reply
      • Excellent, all working now. For some reason though I still needed to add in the rewrite rules for %category%, but hey, it’s all good now!

        11:18 am on July 20th, 2010 Reply
  6. Thank you so much for this post, I’ve read everything out there on custom post types and this cleared up almost everything for me. I still have one piece of functionality left to solve.

    I want to set up archives that work in the same exact way. For example:

    http://shibashake.com/wordpress-theme/galleries/%year%/%monthnum%/%day%/

    Should list the custom posts of that day. This is normal post behavior, but I’m not sure what I should do to set it up for my custom post type.

    12:40 pm on July 7th, 2010 Reply
    • Hmmm, I looked briefly at the wp_get_archives function.

      One way to do what you want is filter the where query argument in the wp_get_archives function call. On line 904 -

      $where = apply_filters('getarchives_where', "WHERE post_type = 'post' AND post_status = 'publish'", $r );

      You can just write a filter into the getarchives_where hook and replace post_type = ‘post’ with your own custom post type. For example something like this -

      add_filter('getarchives_where', 'my_custom_archive', 10, 2);
      function my_custom_archive($where, $r) {
         return str_replace("post_type = 'post'","post_type = 'gallery'",$where);
      }
      10:47 am on July 8th, 2010 Reply
      • Oh cool, that makes a lot of sense, but I’m still missing something. If I put a link in as /galleries/2010/07/08/ it calls my normal archives instead of the new archive function. I think I just need another rewrite rule to tell it to pick up on the word “galleries” being in the url, but I’m not sure how to write that.

        (FYI, I do also have normal archives being used on the page as well so I can’t overwrite them)

        9:08 am on July 12th, 2010 Reply
        • Ahhhh – ok.

          One quick and dirty way to do this is to hook into the query function itself -

          add_filter('request', 'expanded_gallery_archive');

          Within the function you can check for the galleries keyword by checking $_SERVER['REQUEST_URI'], and then just add post_type into the query.

          function expanded_gallery_archive($q) {
          	if (isset($q['year']) && (strpos($_SERVER['REQUEST_URI'], '/galleries') !== FALSE)) {
          		$q['post_type'] = 'gallery';
          	}	
          	return $q;
          }

          But then you want to make sure that the strpos check is totally unique so that it doesn’t screw up other permalinks that may look similar.

          Not a very elegant solution – but it should point you in the right direction. Let us know how it turns out.

          11:03 am on July 12th, 2010 Reply
          • Bingo! that did it. Thank you so much.

            Now I’ve got a bonus question, which just appeared.

            How do I create a custom 404 page to display when I accidentally send someone to a date with no custom-posts?

            This is for listing events and I’ll undoubtedly have dates with no events in which case I would want to show a “try another date” page that would be different from the 404 page for the rest of the site.

            1:48 pm on July 12th, 2010
          • I would look into using the template_redirect action hook. Then within your function you can check for your custom post type archive URL as well as look at the $wp_query global to see if it is a 404 condition.

            Do I get a bonus cup of coffee? ;)

            9:01 pm on July 13th, 2010
          • Absolutely! I just made one. Everything is working beautifully, thank you for all your help.

            9:20 am on July 15th, 2010
  7. Jason

    I’m just trying to add ‘.html’ to custom post type URLs (so they match my existing post permalink structure).

    Any ideas how to add .html?

    4:30 pm on July 3rd, 2010 Reply
    • Just follow the steps above and add .html to your permalink structure. For example set your custom post type permalink structure to -

      /gallery/%gallery%.html

      If you don’t need to process additional tags – then you don’t even need the post_type_link function.

      9:46 pm on July 3rd, 2010 Reply
  8. Great writeup!

    I’m trying to get the following custom permalink structure for 1 of my post custom post types:

    domain.com/post-type/%state%/%postname%/

    Where “state” is a custom taxonomy that behave like categories. Each of my post types will only belong to one “state”.

    Then I could hit domain.com/post-type/%state%/, and it would pull up all of those post types in that have the “state” taxonomy.

    Any ideas?

    10:38 am on June 28th, 2010 Reply
    • Great question by both you and Lane.

      I have not tried adding my own taxonomy tags to the permalink structures, so I can only guess at what would be needed. My guess is that you would need at least two things –

      • Add some code to process the new tag in your equivalent gallery_permalink function. It can be patterned after the %category% tag code.
      • Add a rewrite tag for your taxonomy using the add_rewrite_tag command.
      2:10 pm on June 28th, 2010 Reply
  9. Thank you so much for this. How would you get the category into the permalink? Even better, how would you get a custom taxonomy category in there?

    9:07 pm on June 27th, 2010 Reply
    • Hello Lane,
      The code above will support the %category% tag in the permalink structure.

      Lines 23-38 in the gallery_permalink function deals with assigning categories.

      I have not tried adding in my own taxonomy tags. You will at least need to include similar code (as the category code) to process the new tag in the permalink structure. You may also need to add a rewrite tag.

      2:00 pm on June 28th, 2010 Reply
  10. granulr

    How can I just remove the post type from the url?

    currently i have http://www.mysite.com/directory/california-listings

    and my client needs it to be http://www.mysite.com/california-listings

    11:50 am on June 27th, 2010 Reply
    • You can try following the steps above and passing in /%directory% as your post type permalink structure. However, that has a high probability of conflicting with other objects. For example, your regular post objects may have exactly that same structure.

      It is possible to assign the same permalink structure to multiple object types – but this can get messy very quickly. In particular you would want to hook into the main query function (for example through the request filter), and then alter your query in there to properly assign the right queries to the different object types.

      1:37 pm on June 28th, 2010 Reply
  11. Be aware that $post_id passed by the post_type_link filter is actually the post object, not the ID as the name implies.

    9:32 am on June 26th, 2010 Reply
    • Great catch Andy.
      There seems to be a small bug (I think) in WP core where they are passing in the post object instead of the ID.
      Doing

      $post = get_post($post_id);

      will ensure that $post is always a post object.

      1:31 pm on June 28th, 2010 Reply
  12. Thanks for clearing this up, there are lot of confused developers out there right now trying to figure out the new custom post type functionality.

    9:10 pm on June 25th, 2010 Reply

RSS feed for comments on this post. TrackBack URL

Leave a Reply

search button search button
rss