Shiba

Adventures in WordPress

  • Home
  • Dog
  • Art
  • Contact
  • WordPress Articles
    • WP Plugins
    • WP Programming
    • WP Admin Panels
    • WP Theme Design
    • WP How-To
    • WP Theme Images
You are here: Home / WordPress Admin / Custom Post Type / Custom Post Type Permalinks – Part 2

Custom Post Type Permalinks – Part 2

by ShibaShake 135 Comments

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.

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

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

Comments

1 2 3 … 8 Next »
  1. Evan says

    July 21, 2010 at 9:15 am

    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.

    Reply
    • ShibaShake says

      July 22, 2010 at 2:26 pm

      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.

      Reply
  2. Epic Alex says

    July 19, 2010 at 11:16 am

    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

    Reply
    • ShibaShake says

      July 19, 2010 at 2:12 pm

      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.

      Reply
      • Epic Alex says

        July 20, 2010 at 11:18 am

        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!

  3. Evan says

    July 7, 2010 at 12:40 pm

    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.

    Reply
    • ShibaShake says

      July 8, 2010 at 10:47 am

      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);
      }
      
      Reply
      • Evan says

        July 12, 2010 at 9:08 am

        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)

      • ShibaShake says

        July 12, 2010 at 11:03 am

        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.

      • Evan says

        July 12, 2010 at 1:48 pm

        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.

      • ShibaShake says

        July 13, 2010 at 9:01 pm

        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? 😉

      • Evan says

        July 15, 2010 at 9:20 am

        Absolutely! I just made one. Everything is working beautifully, thank you for all your help.

  4. Jason says

    July 3, 2010 at 4:30 pm

    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?

    Reply
    • ShibaShake says

      July 3, 2010 at 9:46 pm

      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.

      Reply
  5. Andrew says

    June 28, 2010 at 10:38 am

    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?

    Reply
    • ShibaShake says

      June 28, 2010 at 2:10 pm

      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.
      Reply
  6. Lane says

    June 27, 2010 at 9:07 pm

    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?

    Reply
    • ShibaShake says

      June 28, 2010 at 2:00 pm

      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.

      Reply
  7. granulr says

    June 27, 2010 at 11:50 am

    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

    Reply
    • ShibaShake says

      June 28, 2010 at 1:37 pm

      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.

      Reply
  8. Andy Potanin says

    June 26, 2010 at 9:32 am

    Be aware that $post_id passed by the post_type_link filter is actually the post object, not the ID as the name implies.

    Reply
    • ShibaShake says

      June 28, 2010 at 1:31 pm

      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.

      Reply
  9. Andy Potanin says

    June 25, 2010 at 9:10 pm

    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.

    Reply
1 2 3 … 8 Next »

Recent Posts

  • Screenshot of an example article in code view of a modified Gutenberg editor.How to Harness the Power of WordPress Gutenberg Blocks and Combine It with Legacy Free-Form Text
  • Screenshot of the Success, WordPress has been installed page.Migrating Your WordPress Website to Amazon EC2 (AWS)
  • Screenshot of WinSCP for creating a SFTP configuration.How to Set-Up SFTP on Amazon EC2 (AWS)
  • WordPress Gutenberg code view screenshot of this article.How to Prevent Gutenberg Autop from Messing Up Your Code, Shortcodes, and Scripts
  • Screenshot of the Success, WordPress has been installed page.How to Create a WordPress Website on Amazon EC2 (AWS)

Recent Comments

  • Screenshot of the Success, WordPress has been installed page.How to Create a WordPress Website on Amazon EC2 (AWS) (1)
    • Erik
      - Great article. All worked great except for this step:apt install php-mysqlChanging to this fixed it:apt install ...
  • Add Custom Taxonomy Tags to Your WordPress Permalinks (125)
    • Anthony
      - Where does this code go? Like, what exact .php file please?
  • Screenshot of an example article in code view of a modified Gutenberg editor.How to Harness the Power of WordPress Gutenberg Blocks and Combine It with Legacy Free-Form Text (1)
    • tom
      - hi,my experience was like the same, but for me as theme developer the "lazy blocks"(https://wordpress.org/plugins/lazy-blocks/) ...
  • WordPress Custom Taxonomy Input Panels (106)
    • Phil T
      - This is unnecessarily confusing. Why declare a variable with the same name as your taxonomy? Why did you choose a taxonomy ...
  • Create Pop-up Windows in Your WordPress Blog with Thickbox (57)
    • Jim Camomile
      - I have used this tutorial several times and it is one of the few simple and effective ways to add popups with dynamic content, ...

Copyright © 2024 · Genesis Skins by ShibaShake · Terms of Service · Privacy Policy ·