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 the register_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->front variable.

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:

WordPress change permalink structure for a custom post type recommended settings
The above code allows you to change the permalink structure for a custom post type with these permalink settings. Maybe it works with other settings; I don’t know, I just didn’t have a chance to test it.
Misha Rudrastyh

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

Follow me on X