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.
1 2 3 4 5 6 7 | $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 –
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.
1 2 3 4 5 | // 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 –
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 –
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.
This is not what we want, so we set it to false.
Adding the add_permastruct function changes our gallery object permalinks from
to
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 –
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.
Thanks to ShibaShake for sharing it, it was really helpful to me personally. I spent nearly a day implementing this feature for my plugin and now it’s really perfect. Great!
Hi,
Thanks for this!
I have been implementing this but I need more features, for example, I need to display more than on thumbnail per post (we are uing acf and custom get field). We’ve been working on it but we don’t find a way to do it.
We can pay for this modification, if you are interested in, please contact me and I will explain what I need, and you will tell me your price 🙂
Thanks in advance,
Cyril
Hi,
Can you help me with this as a freelance job?
You can contact me on my e-mail address.
Thanks in advance,
Cyril
Hello Cyril, I am not doing any freelance jobs at the moment. Too much on my plate. Good luck with your project.
Hi Shiba,
Great tutorial (and very cool site)!
I’m just wondering – do you have a tutorial on how you can change the default WordPress Media Manager “Link To | Media File” location so it goes to the *large* version of the image, instead of the *original* size? A lot of site authors are now uploading very large 8MB+ images from their cameras, without realising that they’re slowing down their site. That would be really helpful!
An nice solution for a plugin/theme would be an option to set the default size for the Media File URL, so a user could choose to always use one of the other sizes (medium/large/original/custom). Alternatively, an image size dropdown, rather than the text input field, for the user to choose the right size.
Anyway, keep up the great work – it’s appreciated!
Cheers,
Zain
Hi, thanks for this cool tutorial. I was spending 2 hours implementing this feature for my plugin and now it works perfectly. 🙂
HI Its very useful, But is there any way to add a textbox or a new dropdown to the gallery page