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 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:


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)


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

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


I would like


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?

share|improve this question
Please add more details to your question: We have many solutions for this on our site (see the related questions in the sidebar). What have you tried, why didn’t it work? – toscho Aug 28 '12 at 15:19
I've updated my question as per your suggestion. Hopefully it's more clearer now. And yup, I've searched this site for over an hour but most of the related answers address the CPT/taxonomy/post-name permalink scheme. I tried wrangling the code I did find to suite my needs, but don't really know what I'm doing. – bob Aug 28 '12 at 17:53
Well, I spent another hour or so proving to myself that I really need to work on my google-fu. I have failed to find anyone document the permalink scheme I'm trying to achieve. Usually that means I'm going about things the wrong way. Maybe I should try to layout my site structure a different, more WordPress-friendly way? – bob Aug 29 '12 at 7:26
up vote 4 down vote accepted

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

            '[^/]+/' . $this->post_type .'/([^/]+)/?$',
            'index.php?post_type=' . $this->post_type . '&pagename=$matches[1]',

        // 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() )

        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.

share|improve this answer
Wow, cheers for helping me out. Seems to work as intended. I'll have a go at creating additional cpt's using your code as a template. Many thanks – bob Aug 29 '12 at 18:31
Doh, when I use your code example it breaks standard posts and other post types I add categories to. Any ideas why? – bob Sep 4 '12 at 14:45
@bob Interesting. See my update: you have to put the rewrite regex into the slug parameter. Looks a little bit strange, but it works now. – toscho Sep 4 '12 at 21:29

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.