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.

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?


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 ) );
          } while ( $post_name_check );

          $slug = $alt_post_name;

    return $slug;
| improve this answer | |
  • Works perfectly. This was incredibly helpful and thank you for the detail. – JLeonard Nov 15 '12 at 4:00

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.