0

I have a custom taxonomy (video-category) for a custom post type (video), and I'm trying to rewrite their paths in line with the examples below:

Post (Archive): about-us/video-center/category-name

Post (Single): about-us/video-center/category-name/post-title

The post rewrite works as expected using: 'rewrite' => array( 'slug' => 'about-us/video-center/%category%', 'with_front' => false )

However, the taxonomy page is giving me a 404 error with the following: 'rewrite' => array( 'slug' => 'about-us/video-center', 'with_front' => false );

I realised that if I alter the rewrite path for the taxonomy to start with a page other than 'about-us', both redirects work. Unfortunately, I need both the archive and post pages to follow the exact same URL path.

I've created a gist with the full registrations for both the CPT and taxonomy alongside a function which rewrites the %category% placeholder here: https://gist.github.com/graemebryson/92f5da5873a5edf0057d3b93b5cc4fdb

I've read most (if not all) related questions, but have been unable to find anything that allows both the taxonomy and post rewrites to share the same path. If it's not possible via rewrite, do I have any other options for forcing both to share the same URL path?

2 Answers 2

4

(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, '/' ) ), '/' );

        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, '/' ) ), '/' );

        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' );
4
  • 1
    That's massively appreciated. I've a couple of CPTs/rewrites to deal with on this build and it was a massive pain - thanks Sally. Feb 8, 2020 at 15:07
  • 1
    You're welcome. :)
    – Sally CJ
    Feb 8, 2020 at 16:04
  • 1
    I hope you don't mind I revised the answer. But mainly because I've got an updated code and I've linked this answer to other question. :) Cheers!
    – Sally CJ
    Mar 7, 2020 at 9:46
  • 1
    Not at all - thanks for updating. Mar 7, 2020 at 11:06
0

Working solution for WooCommerce CPT product + Taxonomy product_cat.

Setup

Change the Settings > Permalinks structure to the same value for both the products base and the product category base.

Code

function wpse_358157_parse_request( $wp ) {
    // rewrite slug; no trailing slashes
    $path = trim( wc_get_permalink_structure()['product_base'], '/' );

    $taxonomy  = 'product_cat'; // taxonomy slug
    $post_type = 'product';     // 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, '/' ) ), '/' );

        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' );

Thanks @Sally CJ for the perfect generic solution.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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