Force unique page slugs across all post types

I have a site with many custom post types and I need a unique slug for each page.

What I’m seeing is that wordpress only checks for slug conflicts within a post type before appending “-2”, “-3”. So I can have “actor/foo”, “book/foo”, “book/foo-2”. I don’t want that. I want each foo to have its own slug no matter the post type.

Read More

I want to avoid using a rewrite on the slug and I’d prefer not to edit any WP source outside of plugins/themes.

Is this possible?

Related posts

Leave a Reply

1 comment

  1. A unique slug for posts is done by wp_unique_post_slug(). Looking at the source the returned slug is filtered by wp_unique_post_slug. So we can replace this generated slug with our own.

    You’ll notice in the source that attachments are required to have unique slugs across all types, so we’ll just use the code from that.

    The examples you gave seemed to be non-hierarchal – and it’s not clear if you want the slugs to be unique for hierarchal post types (which by default only need to be unique within their tree) – so in the example below I ignore hierarchal post types.

    add_filter('wp_unique_post_slug', 'wpse72553_cross_type_unique_slugs',10,5);
    function wpse72553_cross_type_unique_slugs( $slug, $post_ID, $post_status, $post_type, $post_parent ){
    
          global $wpdb, $wp_rewrite;
    
         //Don't touch hierarchical post types
         $hierarchical_post_types = get_post_types( array('hierarchical' => true) );
         if( in_array( $post_type, $hierarchical_post_types ) )
              return $slug;
    
         if( 'attachment' == $post_type ){
             //These will be unique anyway
             return $slug;
         }
    
    
        $feeds = $wp_rewrite->feeds;
        if ( ! is_array( $feeds ) )
             $feeds = array();
    
    
         //Lets make sure the slug is really unique:
         $check_sql = "SELECT post_name FROM $wpdb->posts WHERE post_name = %s AND ID != %d LIMIT 1";
         $post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, $slug, $post_ID ) );
    
         if ( $post_name_check || in_array( $slug, $feeds) ) {
              $suffix = 2;
    
              do {
                  $alt_post_name = substr ($slug, 0, 200 - ( strlen( $suffix ) + 1 ) ) . "-$suffix";
                  $post_name_check = $wpdb->get_var( $wpdb->prepare($check_sql, $alt_post_name, $post_ID ) );
                  $suffix++;
              } while ( $post_name_check );
    
              $slug = $alt_post_name;
          }
    
        return $slug;
    }