Permalink post_type structure with taxonomy slug in it

Let’s look at the simple example of a eshop on WordPress. The shop has custom post type product and category for products product_cat. By default product links looks like /product/product-name/. How to change it to /{product category name}/{product name}/? For example we want to change /product/burton-cartel/ to /snowboard-bindings/burton-cartel/. The following code will let you do […]

#permalinks  /   18

Let’s look at the simple example of a eshop on WordPress.

The shop has custom post type product and category for products product_cat. By default product links looks like /product/product-name/.

How to change it to /{product category name}/{product name}/?

For example we want to change /product/burton-cartel/ to /snowboard-bindings/burton-cartel/.

The following code will let you do that. The code is for your current theme functions.php file and works great with the following permalink settings:

Recomended permalink settings for the code.

Rewrite Post type links and Change default request

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 ( strpos( $permalink, $post_type_slug ) === FALSE || $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 ) && !empty( $terms ) && is_object( $terms[0] ) ) // 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
 
	$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(
		"
		SELECT ID
		FROM $wpdb->posts
		WHERE post_name = '$slug'
		AND post_type = '$post_type_name'
		"
	);
 
	$terms = wp_get_object_terms( $post_id, $tax_name ); // our post should have the terms
 
 
	if( isset( $slug ) && $post_id && !is_wp_error( $terms ) && !empty( $terms ) ) : // change the query
 
		unset( $query['attachment'] );
		$query[$post_type_name] = $slug;
		$query['post_type'] = $post_type_name;
		$query['name'] = $slug;
 
	endif;
 
	return $query;
}

How to perform 301 redirect from the old post URLs

add_action('template_redirect', 'rudr_post_type_redirect');
 
function rudr_post_type_redirect() {
 
	$post_type_name = 'product'; // specify your own here
	$post_type_slug = 'product'; // here
	$tax_name = 'platform'; // and here
 
	if( strpos( $_SERVER['REQUEST_URI'], $post_type_slug ) === FALSE) // 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
		global $post, $wp_rewrite;
 
		$terms = wp_get_object_terms( $post->ID, $tax_name ); // get terms attached
 
		if ( !is_wp_error( $terms ) && !empty( $terms ) && is_object( $terms[0] ) ) :
 
			wp_redirect( site_url() . '/' . $wp_rewrite->front . '/' . $terms[0]->slug . '/' . $post->post_name, 301 );
			// wp_redirect( get_permalink( $post->ID ), 301 ); // depends on the previous code from this post
 
			exit();
        	endif;
	endif;
 
}

Only the best of WordPress

Subscribe to this weekly newsletter to receive the latest blog posts by email.I respect your privacy. Your email is safe with me.

Comments 18

  • The code works,
    but it turns out this error:
    Notice: Undefined index: attachment in /home2/qgpalazz/public_html/sviluppo_fincos/wp-content/plugins/cpt-immobili/cpt-immobili.php on line 148

    In the line 148:
    $slug = $query[‘attachment’]; // when we change the post type link, WordPress thinks that these are attachment pages

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

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

          /*
           * Rewrite default post type links to use 'category' taxonomy in URL.
           */
          add_filter('post_link', 'default_post_type_permalink', 20, 3);
          add_filter('post_type_link', 'default_post_type_permalink', 20, 3);
          function default_post_type_permalink($permalink, $post_id, $leavename) {
            $post_type_name = 'post'; // post type name, you can find it in admin area or in register_post_type() function
            $tax_name = 'category'; // the categories taxonomy name
            $post = get_post($post_id);
            if ($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 (categories) of this post (post)
                  if (!is_wp_error($terms) && !empty($terms) && is_object($terms[0])) // rewrite only if this post has categories
                    $permalink = str_replace($post->post_name, $terms[0]->slug . '/' . $post->post_name, $permalink);
            return $permalink;
          }
           
          add_filter('request', 'default_post_type_request', 1, 1);
          function default_post_type_request($query){
            global $wpdb;
            $post_type_name = 'post'; // specify your own here
            $tax_name = 'category'; // and here
            $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(
              "
              SELECT ID
              FROM $wpdb->posts
              WHERE post_name = '$slug'
              AND post_type = '$post_type_name'
              "
            );
            $terms = wp_get_object_terms($post_id, $tax_name); // our post should have the terms
            if(isset($slug) && $post_id && !is_wp_error($terms) && !empty($terms)) : // change the query
              unset($query['attachment']);
              $query[$post_type_name] = $slug;
              $query['post_type'] = $post_type_name;
              $query['name'] = $slug;
            endif;
            return $query;
          }
        • I’m glad you’ve figured it out. Thanks for sharing your code.

  • Jamie Mitchell Design November 21, 2017 at 10:47

    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.

      • Jamie Mitchell Design November 21, 2017 at 22:48

        Hi Misha

        is it just the redirect part that needs to be different or the whole thing?

        It’s for a client project.

  • Samjhana Joshi February 8, 2018 at 07:14

    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?

Leave your question or feedback

phpjsHTMLCSSSQLCode
Please, enter a comment
Please, enter a name
Incorrect email