Rewrite custom post & taxonomy to share same URL path

(Revised on March 7th 2020 UTC)

So sharing the same permalink path (or rewrite slug/base as in example.com/<rewrite slug>/<term slug> and example.com/<rewrite slug>/<post slug>) is possible, and here’s the (updated) code which enables you to share the exact same rewrite slug between a taxonomy and a post type:

function wpse_358157_parse_request( $wp ) {
    $path="about-us/video-center"; // rewrite slug; no trailing slashes
    $taxonomy  = 'video-category';        // taxonomy slug
    $post_type="video";                 // post type slug

    if ( preg_match( '#^' . preg_quote( $path, '#' ) . '/#', $wp->request ) &&
        isset( $wp->query_vars[ $taxonomy ] ) ) {
        $slug = $wp->query_vars[ $taxonomy ];
        $slug = ltrim( substr( $slug, strrpos( $slug, "https://wordpress.stackexchange.com/" ) ), "https://wordpress.stackexchange.com/" );

        if ( ! term_exists( $slug, $taxonomy ) ) {
            $wp->query_vars['name']       = $wp->query_vars[ $taxonomy ];
            $wp->query_vars['post_type']  = $post_type;
            $wp->query_vars[ $post_type ] = $wp->query_vars[ $taxonomy ];
            unset( $wp->query_vars[ $taxonomy ] );
        }
    }
}
add_action( 'parse_request', 'wpse_358157_parse_request' );

A brief explanation:

It’s much easier to check if a term exists (than checking if a post exists) by the slug, so in the above code, we check if the current request satisfies a term request (i.e. the taxonomy query var — e.g. video-category — exists in the request and the request path matches the taxonomy’s and post type’s rewrite slug), and if so and that the term doesn’t exist, then we assume it’s a post request.

I.e. We make WordPress treats the request as a request for a post instead of a term.

Additional Notes

  1. Make sure your posts and terms do not share the same slug. Because the posts and terms share the same rewrite slug, so there would be no way to know whether we should load the post or the term.

  2. In the original answer, I mentioned that the taxonomy/term permalink can’t be hierarchical (e.g. example.com/<rewrite slug>/<parent term slug>/<child term slug>/), but actually, it can be hierarchical! I just had no time to thoroughly test that before.. 🙂

    register_taxonomy( 'video-category', 'video', [
            'rewrite'      => [
                'slug'         => 'about-us/video-center',
                'with_front'   => false,
                'hierarchical' => true, // enable hierarchical term permalink
            ],
            'hierarchical' => true,
            ...
        ]
    );
    
  3. I also said that the post type needs to be registered before the taxonomy, but in my recent tests, it’s actually the opposite — I registered the taxonomy first.

    So try to first register the taxonomy and then the post type. If that doesn’t work, then register the post type first.

Full sample code

function my_register_taxonomy() {
    register_taxonomy( 'video-category', 'video', [
            'rewrite'      => [
                'slug'         => 'about-us/video-center',
                'with_front'   => false,
                'hierarchical' => true,
            ],
            'hierarchical' => true,
            'labels'       => [
                'name'          => 'Video Categories',
                'singular_name' => 'Video Category',
            ],
            'show_in_rest' => true,
        ]
    );
}

function my_register_post_type() {
    register_post_type( 'video', [
            'rewrite'      => [
                'slug'       => 'about-us/video-center',
                'with_front' => false,
            ],
            'has_archive'  => 'about-us/video-center',
            'hierarchical' => true,
            'public'       => true,
            'supports'     => [ 'title', 'editor', 'page-attributes' ],
            'labels'       => [
                'name'          => 'Videos',
                'singular_name' => 'Video',
            ],
            'show_in_rest' => true,
        ]
    );
}

add_action( 'init', function () {
    my_register_taxonomy();
    my_register_post_type();

    /* If that doesn't work, you can try switching them:
    my_register_post_type();
    my_register_taxonomy();
    */
} );

function wpse_358157_parse_request( $wp ) {
    $path="about-us/video-center"; // rewrite slug; no trailing slashes
    $taxonomy  = 'video-category';        // taxonomy slug
    $post_type="video";                 // post type slug

    if ( preg_match( '#^' . preg_quote( $path, '#' ) . '/#', $wp->request ) &&
        isset( $wp->query_vars[ $taxonomy ] ) ) {
        $slug = $wp->query_vars[ $taxonomy ];
        $slug = ltrim( substr( $slug, strrpos( $slug, "https://wordpress.stackexchange.com/" ) ), "https://wordpress.stackexchange.com/" );

        if ( ! term_exists( $slug, $taxonomy ) ) {
            $wp->query_vars['name']       = $wp->query_vars[ $taxonomy ];
            $wp->query_vars['post_type']  = $post_type;
            $wp->query_vars[ $post_type ] = $wp->query_vars[ $taxonomy ];
            unset( $wp->query_vars[ $taxonomy ] );
        }
    }
}
add_action( 'parse_request', 'wpse_358157_parse_request' );

Leave a Comment