How to achieve this permalink -> category-name/custom-post-type-name/post-name

I am trying to use categories to provide the structure for my site. I will create sections on my site called England, Wales, Scotland and Ireland . These will be categories. My posts will use custom post types – Video, Guide, Event, Clubs etc.

So, if I create a post called ‘Trouble down south’ using the ‘Video’ CPT, attach it to the ‘England’ category, I would like the slug format to be:

Read More

category-name/custom-post-type-name/post-name

e.g. mysite.com/england/video/trouble-down-south

I would also like the following permalinks to work like so –

mysite.com/england/ – show all posts in that category regardless of CPT

mysite.com/video/ – show all Video CPT posts regardless of category

Sticking with my example, I create my Video CPT like so:

add_action( 'init', 'register_cpt_video' );

function register_cpt_video() {

    $labels = array( 
        'name' => _x( 'videos', 'video' ),

        some code...

    );

    $args = array( 

        some code...

        'taxonomies' => array( 'category' ),

        some code...

        'has_archive' => true,
        'rewrite' => array( 
            'slug' => 'video', 
            'with_front' => false,
            'feeds' => true,
            'pages' => true
        ),
        'capability_type' => 'post'
    );

    register_post_type( 'video', $args );
}

This almost does what I need. I get the correct listings at

mysite.com/england (i.e category archive page)

and

mysite.com/video (i.e CPT archive page)

Unfortunately when I click through to the actual post, the slug shows as

mysite.com/video/trouble-down-south

I would like

mysite.com/england/video/trouble-down-south

I’ve tried using the following rewrite slug

'slug' => '%category%/video',

This does not work. %category% gets literally inserted into the url without being converted into the actual category name.

I have searched this site for a solution but haven’t found any. Most related answers seem to address appending taxonomies to the CPT slug.

i.e mysite.com/video/england/trouble-down-south

They seem to achieve this by explicitly prefixing the CPT name to the taxonomy rewrite slug which exploits the cascade-like nature of the rewrite rules (plus some other wizardry I don’t understand.)

Anyhow, nothing I have found has addressed my requirement of category-name/custom-post-type-name/post-name, plus the archive pages I require.

Any ideas how to achieve my desired permalink structure?

Related posts

Leave a Reply

2 comments

  1. You have to filter 'post_type_link' to create the appropriate permalink. See this answer for a similar example.

    The following code works:

    add_action( 'init', array ( 'WPSE_63343', 'init' ) );
    
    class WPSE_63343
    {
        protected $post_type = 'video';
    
        public static function init()
        {
            new self;
        }
    
        public function __construct()
        {
            $args = array (
                'public' => TRUE,
                'rewrite' => array(
                    'slug' => '[^/]+/' . $this->post_type .'/([^/]+)/?$'
                ),
                'has_archive' => TRUE,
                'supports' => array( 'title', 'editor' ),
                'taxonomies' => array( 'category' ),
                'labels' => array (
                    'name' => 'Video',
                    'singular_name' => 'Video'
                )
            );
            register_post_type( $this->post_type, $args );
    
            add_rewrite_rule(
                '[^/]+/' . $this->post_type .'/([^/]+)/?$',
                'index.php?post_type=' . $this->post_type . '&pagename=$matches[1]',
                'top'
            );
    
            // Inject our custom structure.
            add_filter( 'post_type_link', array ( $this, 'fix_permalink' ), 1, 2 );
    
            # uncomment for debugging
            # add_action( 'wp_footer', array ( $this, 'debug' ) );
        }
    
        /**
         * Print debug data into the 404 footer.
         *
         * @wp-hook wp_footer
         * @since   2012.09.04
         * @return  void
         */
        public function debug()
        {
            if ( ! is_404() )
            {
                return;
            }
    
            global $wp_rewrite, $wp_query;
    
            print '<pre>' . htmlspecialchars( print_r( $wp_rewrite, TRUE ) ) . '</pre>';
            print '<pre>' . htmlspecialchars( print_r( $wp_query, TRUE ) ) . '</pre>';
        }
    
        /**
         * Filter permalink construction.
         *
         * @wp-hook post_type_link
         * @param  string $post_link default link.
         * @param  int    $id Post ID
         * @return string
         */
        public function fix_permalink( $post_link, $id = 0 )
        {
            $post = &get_post( $id );
            if ( is_wp_error($post) || $post->post_type != $this->post_type )
            {
                return $post_link;
            }
            // preview
            empty ( $post->slug )
            and ! empty ( $post->post_title )
            and $post->slug = sanitize_title_with_dashes( $post->post_title );
    
            $cats = get_the_category( $post->ID );
    
            if ( ! is_array( $cats ) or ! isset ( $cats[0]->slug ) )
            {
                return $post_link;
            }
    
            return home_url(
                user_trailingslashit( $cats[0]->slug . '/' . $this->post_type . '/' . $post->slug )
            );
        }
    }
    

    Don’t forget to visit the permalink settings once to refresh the stored rewrite rules.

  2. add_action( 'init', 'register_my_types' );
    function register_my_types() {
        register_post_type( 'recipes',
            array(
                'labels' => array(
                    'name' => __( 'Recipes' ),
                    'singular_name' => __( 'Recipee' )
                ),
                'public' => true,
                'has_archive' => true,
            )
        );
        register_taxonomy( 'country', array( 'recipes' ), array( 
                'hierarchical' => true, 
                'label' => 'Country'
            )
        );
    }
    // Add our custom permastructures for custom taxonomy and post
    add_action( 'wp_loaded', 'add_clinic_permastructure' );
    function add_clinic_permastructure() {
        global $wp_rewrite;
        add_permastruct( 'country', 'recipes/%country%', false );
        add_permastruct( 'recipes', 'country/%country%/recipes/%recipes%', false );
    }
    // Make sure that all links on the site, include the related texonomy terms
    add_filter( 'post_type_link', 'recipe_permalinks', 10, 2 );
    function recipe_permalinks( $permalink, $post ) {
        if ( $post->post_type !== 'recipes' )
            return $permalink;
        $terms = get_the_terms( $post->ID, 'country' );
    
        if ( ! $terms )
            return str_replace( '%country%/', '', $permalink );
        //$post_terms = array();
        foreach ( $terms as $term )
            $post_terms = $term->slug;
        print_r($permalink);
        return str_replace( '%country%',  $post_terms , $permalink );
    }
    // Make sure that all term links include their parents in the permalinks
    add_filter( 'term_link', 'add_term_parents_to_permalinks', 10, 2 );
    function add_term_parents_to_permalinks( $permalink, $term ) {
        $term_parents = get_term_parents( $term );
        foreach ( $term_parents as $term_parent )
            $permlink = str_replace( $term->slug, $term_parent->slug . ',' . $term->slug, $permalink );
        return $permlink;
    }
    // Helper function to get all parents of a term
    function get_term_parents( $term, &$parents = array() ) {
        $parent = get_term( $term->parent, $term->taxonomy );
    
        if ( is_wp_error( $parent ) )
            return $parents;
    
        $parents[] = $parent;
        if ( $parent->parent )
            get_term_parents( $parent, $parents );
        return $parents;
    }
    

    above code paste in theme function.php file and change the permalink option /%category/%postname%/ %