Change Permalink Structure for a Custom Post Type
In this tutorial I am going to show you the ways that allow you to change the permalink structure for a custom post type on your WordPress website. First of all, we’re going to do some customization with the help of the register_post_type() function and then continue to a custom code snippet.
Parameter rewrite in register_post_type() function
Let’s take a look at the rewrite parameter of the register_post_type() function first before jumping straight to the custom coding because it also allows you to change the permalink structure for a custom post type.
It accepts an array of values, and the only values we’re interested in here are slug and with_front.
slug– it is basically the part of a custom post type URLs/slug/cpt-post-name, so we can change it easily in theregister_post_type()function (or whatever plugin you’re using for that purpose).with_front– allows you to add one more prefix into CPT permalinks which is the value of the$wp_rewite->frontvariable.
Here was the boring part, I am pretty sure you already know about the rewrite parameter and can work with it. Now let’s continue to the interesting part.
Custom Post Type Permalink Structure with a Taxonomy Slug
As an example let’s take a look at a simple WooCommerce store. The store has the custom post type product registered and a category for products (taxonomy) product_cat.
By default, the product URLs look like /product/product name.
How to change it to /product category name/product name?
Below is the code which allows us to do that. If you don’t know where to insert this code, please check the guide. One more thing: after inserting this code into your website, don’t forget to refresh the cache of the permalinks; in order to do so, you need to go to the Settings > Permalinks page and hit “Save changes” button even without changing anything.
add_filter( 'post_link', 'rudr_post_type_permalink', 20, 3 );
add_filter( 'post_type_link', 'rudr_post_type_permalink', 20, 3 );
function rudr_post_type_permalink( $permalink, $post_id, $leavename ) {
$post_type_name = 'product'; // post type name, you can find it in admin area or in register_post_type() function
$post_type_slug = 'product'; // the part of your product URLs, not always matches with the post type name
$tax_name = 'product_cat'; // the product categories taxonomy name
$post = get_post( $post_id );
if( false === strpos( $permalink, $post_type_slug ) || $post->post_type !== $post_type_name ) { // do not make changes if the post has different type or its URL doesn't contain the given post type slug
return $permalink;
}
$terms = wp_get_object_terms( $post->ID, $tax_name ); // get all terms (product categories) of this post (product)
if( ! is_wp_error( $terms ) && $terms ) { // rewrite only if this product has categories
$permalink = str_replace( $post_type_slug, $terms[0]->slug, $permalink );
}
return $permalink;
}
add_filter( 'request', 'rudr_post_type_request', 1, 1 );
function rudr_post_type_request( $query ){
global $wpdb;
$post_type_name = 'product'; // specify your own here
$tax_name = 'product_cat'; // and here
if( empty( $query[ 'attachment' ] ) ) {
return $query;
}
$slug = $query[ 'attachment' ]; // when we change the post type link, WordPress thinks that these are attachment pages
// get the post with the given type and slug from the database
$post_id = $wpdb->get_var(
$wpdb->prepare(
"
SELECT ID
FROM $wpdb->posts
WHERE post_name = %s
AND post_type = %s
"
),
$slug,
$post_type_name
);
if( ! $post_id ) {
return $query;
}
$terms = wp_get_object_terms( $post_id, $tax_name ); // our post should have the terms
// change the query
if( ! is_wp_error( $terms ) && $terms ) {
unset( $query[ 'attachment' ] );
$query[ $post_type_name ] = $slug;
$query[ 'post_type' ] = $post_type_name;
$query[ 'name' ] = $slug;
}
return $query;
}Also, let’s perform 301 redirects from the old post URLs to rewritten ones:
add_action( 'template_redirect', 'rudr_post_type_redirect' );
function rudr_post_type_redirect() {
global $post, $wp_rewrite;
$post_type_name = 'product'; // specify your own here
$post_type_slug = 'product'; // here
$taxonomy_name = 'platform'; // and here
if( false === strpos( $_SERVER[ 'REQUEST_URI' ], $post_type_slug ) ) { // do not redirect if the URL doesn't contain the given post type slug
return;
}
if( is_singular( $post_type_name ) ) { // if post type page
$terms = wp_get_object_terms( $post->ID, $taxonomy_name ); // get terms attached
if( ! is_wp_error( $terms ) && $terms ) {
wp_safe_redirect( site_url( "/{$wp_rewrite->front}/{$terms[0]->slug}/{$post->post_name}" ), 301 );
// wp_safe_redirect( get_permalink( $post->ID ), 301 ); // depends on the previous code from this post
exit;
}
}
}However, I only tested this code with the following permalink settings, in your case, it could be different:

Misha Rudrastyh
Hey guys and welcome to my website. For more than 15 years I've been doing my best to share with you some superb WordPress guides and tips for free.
Need some developer help? Contact me
The code works,
but it turns out this error:
Notice: Undefined index: attachment in … on line 148
How we can resolve the error?
Thanks,
Riccardo
other request:
If I call the page with the same name as the URL changes,
It can ensure that the url remains the same because there is already the beginning of taxonomy url that holds different url?
Thanks,
Riccardo
Hi Riccardo,
to prevent “Notice: Undefined index” you may turn off WP_DEBUG in
wp-config.php, or in my code add the condition:if( isset( $query['attachment'] ) ).I don’t recommend to name the page the same name as the URL slugs.
There is a problem when you have 2 post types… For one i need to add category and for another one i need to just remove the base slug.
The task becomes more difficult when you work with 2 and more post types/taxonomies. Actually you can just copy the code for another post type.
Hmm.. I think I will publish a simple plugin for that later.
Hehe. Thank you!
Show this error “Oops! That page can’t be found” ???
Settings > Permalinks, then Save Changes
Hi there, is there an easy way to modify this code to work with a custom structure of
/%category%/%postname%/? Or if that is impossible, is there a way to re-add/%category%/to just the default post type? Any help is greatly appreciated. And thanks for sharing this code!Hi Chris,
Sorry, maybe I just do not understand you, but the code in this post is just for this purpose.
Oh I was referring to the built in post type, not a custom one. My site also uses a the built in post type with categories and the permalink structure was set up to be:
/%category%/%postname%/.Your code works perfectly for my custom post type but I had to change
/%category%/%postname%/to/%postname%/in my permalink settings to get it to work so now all of my built in WP posts are just domain.com/postname/ instead of doamin.com/categoryname/postname/.Any way you can think of to get around that?
I appear to have solved it by reusing your code once more to rewrite the built in post type to have
/%category%/used before/%postname%/essentially bypassing WP permalink setting requirements.I’m glad you’ve figured it out.
Hi !
I have found this works really well, better than all others I tested, think because yours has the redirect.
but one issue I’m having is I get a 404 on child posts of a parent post (hierarchical cpt)
any ideas?
Thanks heaps
Hi Jamie,
For any hierarchical items (no matter post types or terms) the code will be different. Unfortunately I do not have ready code for you.
Hi Misha
is it just the redirect part that needs to be different or the whole thing?
It’s for a client project.
Hello,
Thanks for the wonderful work. It works perfectly fine but is not working when I have to parent taxonomy as well to the permalink.
e.g. f/news/post-name
This throws 404 error.
How can this be solved?
Hello,
If you have parent taxonomies, the code will be different. If you need my personal help with the code, you can contact me by email.
Hi, thanks for this tutorial, it`s very helpful! Is there some way to make show subtax slug in url also?
hey thank you so much for the code.. its really very helpful to me..