Issues when rewrite rules collide?

Is it a problem to have a custom taxonomy and a custom post type use the same rewrite structure?

I have a custom taxonomy people and a custom post type people_bio. The idea is that you get a list of posts about a person with a short biography on top of the page. I combine them in my taxonomy-people.php template file. The permalink is /people/[person-slug].

Both the custom taxonomy and the custom post type have the rewrite argument set to array('slug' => 'people'). This appears to work: get_term_link('seth-godin', 'people') returns /people/seth-godin/, and for a custom post type with slug seth-godin, get_permalink() also returns /people/seth-godin/. The taxonomy is defined first, and it seems to “win”: on a /people/[slug] page, is_tax() is true, while is_single() is false.

So, it works, but I don’t feel comfortable with it. Is someone more experienced with the rewrite engine, and can you tell me whether this might break other things?

The relevant part of the plugin file, called in the init action:

        'labels' => array(
            'name' => 'People',
            'singular_name' => 'Person',
            'search_items' => 'Search people',
            'popular_items' => 'Popular people',
            'all_items' => 'All people',
            'parent_item' => null,
            'parent_item_colon' => null,
            'edit_item' => 'Edit person',
            'update_item' => 'Update person',
            'add_new_item' => 'Add person',
            'new_item_name' => 'New person',
            'separate_items_with_commas' => 'Separate people with commas',
            'add_or_remove_items' => 'Add or remove people',
            'choose_from_most_used' => 'Choose from the most used people',
        'public' => true,
        'show_ui' => true,
        'show_tagcloud' => true,
        'hierarchical' => false,
        'update_count_callback' => '',
        'rewrite' => array(
            'slug' => 'people',
            'with_front' => true,
        'query_var' => 'people',
        'capabilities' => array(),
        'show_in_nav_menus' => true,

        'label' => 'People Bio',
        'labels' => array(
            'name' => 'Biographies',
            'singular_name' => 'Biography',
            'add_new' => 'Add new',
            'add_new_item' => 'Add new biography',
            'edit_item' => 'Edit biography',
            'new_item' => 'New biography',
            'view_item' => 'View biography',
            'search_items' => 'Search biographies',
            'not_found' => 'No biographies found',
            'not_found_in_trash' => 'No biographies found in trash',
            'parent_item_colon' => null,
        'description' => 'Biography pages of interesting people',
        'public' => true,
        'exclude_from_search' => true,
        'publicly_queryable' => true,
        'show_ui' => true,
        'menu_position' => null,
        'menu_icon' => null,
        'capability_type' => 'post',
        'capabilities' => array(),
        'hierarchical' => false,
        'supports' => array(
        'register_meta_box_cb' => null,
        'taxonomies' => array(),
        'permalink_epmask' => EP_PERMALINK,
        //'rewrite' => false,
        'rewrite' => array(
            'slug' => 'people',
            'with_front' => true,
        'query_var' => true,
        'can_export' => true,
        'show_in_nav_menus' => true,

register_taxonomy_for_object_type('people', 'people_bio');

(I always use all parameters with register_*(), many with their default values, as an extra documentation, as long as the Codex is not up-to-date)

The taxonomy-people.php template file:


$people_biography = get_posts(array(
    'numberposts' => -1,
    'post_type' => 'people_bio',
    'taxonomy' => 'people',
    'term' => $wp_query->get_queried_object()->slug,
<div class="container_24">
    <div class="grid_18" id="content" role="main">
        <?php if ($people_biography) :
            foreach ($people_biography as $bio) : ?>
                <h1><?php echo get_the_title($bio->ID); ?></h1>
                echo get_the_post_thumbnail($bio->ID);
                echo apply_filters('the_content', $bio->post_content); ?>
         else: ?>
            <h1><?php esc_html_e($wp_query->get_queried_object()->name); ?></h1>
        <?php endif; ?>
        <?php get_template_part( 'loop', 'archive' ); ?>
    </div><!-- .content -->

    <div class="grid_6" id="default_sidebar">
        <?php dynamic_sidebar('default-sidebar'); ?>
    </div><!-- #default_sidebar -->
</div><!-- .container_24 -->
<div class="clear"></div>

Update: The generated rewrite rules

The output of my Rewrite Analyzer seems to tell me the taxonomy “wins” for regular taxonomy pages (which is what I noticed), but the custom post type gets all other URLs (including second pages, feeds, …). This is what I was afraid of, and which I will need to investigate further.
Rewrite rules for people taxonomy and post type

Solutions Collecting From Web of "Issues when rewrite rules collide?"

Question: Is it a problem to have a custom taxonomy and a custom post type use the same rewrite structure?

Answer: Yes.

If they share the exact same structure, you will get a race condition, so one of those won’t work. But I assume that your scenario does not automatically lead to the same rewrite rules as the two things are differently prefixed.

This happens because the permastructure rewrite rules (which includes those created by registering post_types and taxonomies) are run through an array_merge(). The array_merge will replace any rewrite_rules with the same regex in the current location of the array, but add any that don’t conflict to the end of the array.

Your best option may be to set the ‘rewrite’ argument to false for one of the two registrations and adding separate code to add any missing needed rewrite rules to the rewrite_rules_top.

Yes, this will give issues. The four rewrite rules that are generated by the custom taxonomy are also used by the custom post type. The one that you register first gets overwritten by the one that you register after that. Depending on the exact configuration (hierarchical vs. non-hierarchical) similar rules might end up in the final rewrite rules list, but only the first one will “win”.

This can have the effect that /people/seth-godin/ queries for a custom taxonomy but /people/seth-godin/page/2/ queries for a custom post type.