WordPress Development Stack Exchange is a question and answer site for WordPress developers and administrators. Join them; it only takes a minute:

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

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?

share|improve this question
up vote 3 down vote accepted

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;
share|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 posting your answer, you agree to the privacy policy and terms of service.

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