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 know people have asked this before and have gone as far as adding the custom post type, and rewrite for permalink.

The problem is I have 340 existing categories which I would like to continue using. I used to be able to see /category/subcategory/postname

Now I have the slug of customposttype/postname. Selecting the category no longer shows up in permalink...I've not changed the permalink setting in admin to anything different.

Is there something I'm missing or need to add to this code?

function jcj_club_post_types() {
    register_post_type( 'jcj_club', array(
        'labels' => array(
            'name' => __( 'Jazz Clubs' ),
            'singular_name' => __( 'Jazz Club' ),
            'add_new' => __( 'Add New' ),
            'add_new_item' => __( 'Add New Jazz Club' ),
            'edit' => __( 'Edit' ),
            'edit_item' => __( 'Edit Jazz Clubs' ),
            'new_item' => __( 'New Jazz Club' ),
            'view' => __( 'View Jazz Club' ),
            'view_item' => __( 'View Jazz Club' ),
            'search_items' => __( 'Search Jazz Clubs' ),
            'not_found' => __( 'No jazz clubs found' ),
            'not_found_in_trash' => __( 'No jazz clubs found in Trash' ),
            'parent' => __( 'Parent Jazz Club' ),
        'public' => true,
        'show_ui' => true,
        'publicly_queryable' => true,
        'exclude_from_search' => false,
        'menu_position' => 5,
        'query_var' => true,
        'supports' => array( 
        'rewrite' => array( 'slug' => 'jazz-clubs-in', 'with_front' => true ),
        'taxonomies' => array( 'category','post_tag'),
        'can_export' => true,
share|improve this question
this may be a silly question, but have you flushed your rewrites? – Kristina Childs Dec 19 '12 at 22:45
Recently, I face this issue. Solved! [#188834][1] [1]: wordpress.stackexchange.com/questions/94817/… – maheshwaghmare May 20 '15 at 7:02
up vote 14 down vote

There are 2 points of attack to cover when you are adding custom post type rewrite rules:

Rewrite rules

This happens when the rewrite rules are being generated in wp-includes/rewrite.php in WP_Rewrite::rewrite_rules(). WordPress allows you to filter the rewrite rules for specific elements like posts, pages and various types of archive. Where you see posttype_rewrite_rules the posttype part should be the name of your custom post type. Alternatively you can use the post_rewrite_rules filter as long as you don't obliterate the standard post rules too.

Next we need the function to actually generate the rewrite rules:

// add our new permastruct to the rewrite rules
add_filter( 'posttype_rewrite_rules', 'add_permastruct' );

function add_permastruct( $rules ) {
    global $wp_rewrite;

    // set your desired permalink structure here
    $struct = '/%category%/%year%/%monthnum%/%postname%/';

    // use the WP rewrite rule generating function
    $rules = $wp_rewrite->generate_rewrite_rules(
        $struct,       // the permalink structure
        EP_PERMALINK,  // Endpoint mask: adds rewrite rules for single post endpoints like comments pages etc...
        false,         // Paged: add rewrite rules for paging eg. for archives (not needed here)
        true,          // Feed: add rewrite rules for feed endpoints
        true,          // For comments: whether the feed rules should be for post comments - on a singular page adds endpoints for comments feed
        false,         // Walk directories: whether to generate rules for each segment of the permastruct delimited by '/'. Always set to false otherwise custom rewrite rules will be too greedy, they appear at the top of the rules
        true           // Add custom endpoints

    return $rules;

The main thing to watch out for here if you decide to play around is the 'Walk directories' boolean. It generates rewrite rules for each segment of a permastruct and can cause rewrite rule mismatches. When a WordPress URL is requested the rewrite rules array is checked from top to bottom. As soon as a match is found it will load whatever it has come across so for example if your permastruct has a greedy match eg. for /%category%/%postname%/ and walk directories is on it will output rewrite rules for both /%category%/%postname%/ AND /%category%/ which will match anything. If that happens too early you're screwed.


This is the function that parses the post type permalinks and converts a permastruct (eg '/%year%/%monthnum%/%postname%/') into an actual URL.

The next part is a simple example of what would ideally be a version of the get_permalink() function found in wp-includes/link-template.php. Custom post permalinks are generated by get_post_permalink() which is a much watered down version of get_permalink(). get_post_permalink() is filtered by post_type_link so we're using that to make a custom permastructure.

// parse the generated links
add_filter( 'post_type_link', 'custom_post_permalink', 10, 4 );

function custom_post_permalink( $permalink, $post, $leavename, $sample ) {

    // only do our stuff if we're using pretty permalinks
    // and if it's our target post type
    if ( $post->post_type == 'posttype' && get_option( 'permalink_structure' ) ) {

        // remember our desired permalink structure here
        // we need to generate the equivalent with real data
        // to match the rewrite rules set up from before

        $struct = '/%category%/%year%/%monthnum%/%postname%/';

        $rewritecodes = array(

        // setup data
        $terms = get_the_terms($post->ID, 'category');
        $unixtime = strtotime( $post->post_date );

        // this code is from get_permalink()
        $category = '';
        if ( strpos($permalink, '%category%') !== false ) {
            $cats = get_the_category($post->ID);
            if ( $cats ) {
                usort($cats, '_usort_terms_by_ID'); // order by ID
                $category = $cats[0]->slug;
                if ( $parent = $cats[0]->parent )
                    $category = get_category_parents($parent, false, '/', true) . $category;
            // show default category in permalinks, without
            // having to assign it explicitly
            if ( empty($category) ) {
                $default_category = get_category( get_option( 'default_category' ) );
                $category = is_wp_error( $default_category ) ? '' : $default_category->slug;

        $replacements = array(
            date( 'Y', $unixtime ),
            date( 'm', $unixtime ),

        // finish off the permalink
        $permalink = home_url( str_replace( $rewritecodes, $replacements, $struct ) );
        $permalink = user_trailingslashit($permalink, 'single');

    return $permalink;

As mentioned this a very simplified case for generating a custom rewrite ruleset and permalinks, and is not particularly flexible but it should be enough to get you started.


I wrote a plugin that lets you define permastructs for any custom post types, but like you can use %category% in the permalink structure for posts my plugin supports %custom_taxonomy_name% for any custom taxonomies you have too where custom_taxonomy_name is the name of your taxonomy eg. %club%.

It will work as you'd expect with hierarchical/non-hierarchical taxonomies.


share|improve this answer
the plugin is great, but could you explain how to fix the issue in the question without your plugin? – Eugene Manuilov Dec 20 '12 at 13:39
I agree that it's great there is plugin to address it (I have it bookmarked and it is first that came to my mind on this Q), but the answer would benefit from brief summary of what the issue is and how plugin conquered it. :) – Rarst Dec 22 '12 at 14:30
@EugeneManuilov Alright, sorry it's a lengthy answer. That's with me stripping it down to the basics too! – sanchothefat Dec 24 '12 at 2:01
It looks like the first $permalink = home_url(... gets overridden by $permalink = user_trailingslashit(... and never used. Or am I missing something? $post_link isn't even defined. Was it supposed to be $permalink = user_trailingslashit( $permalink, 'single' ); ? – Ian Dunn Feb 5 '13 at 23:07
Good catch, it should be $permalink not $post_link. Cheers :) – sanchothefat Feb 6 '13 at 10:50

I have found a SOLUTION!!!

(After endless research.. I can have CUSTOM POST TYPE permalinks like:

here the code (in functions.php or plugin):

//===STEP 1 (affect only these CUSTOM POST TYPES)
$GLOBALS['my_post_typesss__MLSS'] = array('my_product1','....');

//===STEP 2  (create desired PERMALINKS)
add_filter('post_type_link',    'my_func88888', 6, 4 );
                        function my_func88888( $post_link, $post, $sdsd){
    if (!empty($post->post_type) && in_array($post->post_type, $GLOBALS['my_post_typesss']) ) {  
        $SLUGG = $post->post_name;
        $post_cats = get_the_category($id);     
        if (!empty($post_cats[0])){ $target_CAT= $post_cats[0];
                $SLUGG =  $target_CAT->slug .'/'.$SLUGG; 
                if  (!empty($target_CAT->parent)) {$target_CAT = get_term( $target_CAT->parent, 'category');}   else {break;}
            $post_link= get_option('home').'/'. urldecode($SLUGG);
    return  $post_link;

// STEP 3  (by default, while accessing:  "EXAMPLE.COM/category/postname"     WP thinks, that a standard post is requested. So, we are adding CUSTOM POST TYPE into that query.
add_action('pre_get_posts', 'my_func4444',  12); 
                    function my_func4444($q){     
    if ($q->is_main_query() && !is_admin() && $q->is_single){
        $q->set( 'post_type',  array_merge(array('post'), $GLOBALS['my_post_typesss'] )   );
    return $q;
share|improve this answer

Well,This is what I did in a theme,may it helps you! just add the code to your function.php,any question, please leave a comment below!

    //////////CUSTOM POST TYPE LABELS////////
    define('CUSTOM_TAXONOMY_SEARCH_ITEM',__('Search Category'));
    define('CUSTOM_POST_ADD_NEW',__('Add Event'));
    define('CUSTOM_POST_ADD_NEW_ITEM',__('Add New Event'));
    define('CUSTOM_POST_EDIT_ITEM',__('Edit Event'));
    define('CUSTOM_POST_NEW_ITEM',__('New Event'));
    define('CUSTOM_POST_VIEW_ITEM',__('View Event'));
    define('CUSTOM_POST_SEARCH_ITEM',__('Search Events'));
    define('CUSTOM_POST_NOT_FOUND',__('No Events found'));
    define('CUSTOM_POST_NOT_FOUND_IN_TRASH',__('No Events found in trash'));
    define('CUSTOM_TAXONOMY_LABEL',__('Event Category'));
    define('CUSTOM_TAXONOMY_NAME',__('Event Categories'));
    define('CUSTOM_TAXONOMY_SINGULAR_NAME',__('Event Category'));
    define('CUSTOM_TAXONOMY_POPULAR_ITEM',__('Popular Event Categories'));
    define('CUSTOM_TAXONOMY_ALL_ITEMS',__('All Event Categories'));
    define('CUSTOM_TAXONOMY_PARENT_ITEM',__('Parent Event Category'));
    define('CUSTOM_TAXONOMY_PARENT_ITEM_COLON',__('Parent Event Category:'));
    define('CUSTOM_TAXONOMY_EDIT_ITEM',__('Edit Event Category'));
    define('CUSTOM_TAXONOMY_UPDATE_ITEM',__('Update Event Category'));
    define('CUSTOM_TAXONOMY_ADD_NEW_ITEM',__('Add New Event Category'));
    define('CUSTOM_TAXONOMY_NEW_ITEM_NAME',__('New Event Category Name'));
    define('CUSTOM_TAG_LABEL',__('Event Tags'));
    define('CUSTOM_TAG_NAME',__('Event Tags'));
    define('CUSTOM_TAG_SINGULAR_NAME',__('Event Tags'));
    define('CUSTOM_TAG_SEARCH_ITEMS',__('Search Event Tags'));
    define('CUSTOM_TAG_POPULAR_ITEMS',__('Popular Event Tags'));
    define('CUSTOM_TAG_ALL_ITEMS',__('All Event Tags'));
    define('CUSTOM_TAG_PARENT_ITEM',__('Parent Event Tags'));
    define('CUSTOM_TAG_PARENT_ITEM_COLON',__('Parent Event Tags:'));
    define('CUSTOM_TAG_EDIT_ITEM',__('Edit Event Tags'));
    define('CUSTOM_TAG_UPDATE_ITEM',__('Update Event Tags'));
    define('CUSTOM_TAG_ADD_NEW_ITEM',__('Add New Event Tags'));
    define('CUSTOM_TAG_NEW_ITEM_NAME',__('New Event Tags Name'));

    add_action("init", "custom_posttype_menu_wp_admin");
    function custom_posttype_menu_wp_admin(){
        //===============EVENT SECTION START================
        register_post_type( CUSTOM_POST_TYPE1,
        array(  'label'             => CUSTOM_POST_LABEL,
            'labels'            => array(   'name'                  => CUSTOM_POST_NAME,//
            'singular_name'         => CUSTOM_POST_SINGULAR_NAME,
            'add_new'               =>  CUSTOM_POST_ADD_NEW,
            'add_new_item'          =>  CUSTOM_POST_ADD_NEW_ITEM,
            'edit'                  =>  CUSTOM_POST_EDIT,
            'edit_item'             =>  CUSTOM_POST_EDIT_ITEM,
            'new_item'              =>  CUSTOM_POST_NEW_ITEM,
            'view_item'             =>  CUSTOM_POST_VIEW_ITEM,
            'search_items'          =>  CUSTOM_POST_SEARCH_ITEM,
            'not_found'             =>  CUSTOM_POST_NOT_FOUND,
            'not_found_in_trash'    =>  CUSTOM_POST_NOT_FOUND_IN_TRASH  ),
            'public'            => true,
            'can_export'        => true,
            'show_ui'           => true, // UI in admin panel
            '_builtin'          => false, // It's a custom post type, not built in
            '_edit_link'        => 'post.php?post=%d',
            'capability_type'   => 'post',
            'menu_icon'         => get_bloginfo('template_url').'/images/favicon.ico',
            'hierarchical'      => false,
            'rewrite'           => array(   "slug" => CUSTOM_POST_TYPE1 ), // Permalinks
            'query_var'         => CUSTOM_POST_TYPE1, // This goes to the WP_Query schema
            'supports'          => array(   'title',
            'revisions') ,
            'show_in_nav_menus' => true ,
            'taxonomies'        => array(CUSTOM_CATEGORY_TYPE1,CUSTOM_TAG_TYPE1),
            'menu_position' => 4

        // Register custom taxonomy
        register_taxonomy(  CUSTOM_CATEGORY_TYPE1,
        array(  CUSTOM_POST_TYPE1   ),
        array ( "hierarchical"      => true,
            "label"             => CUSTOM_TAXONOMY_LABEL,
            'labels'            => array(   'name'              =>  CUSTOM_TAXONOMY_NAME,
            'singular_name'     =>  CUSTOM_TAXONOMY_SINGULAR_NAME,
            'search_items'      =>  CUSTOM_TAXONOMY_SEARCH_ITEM,
            'popular_items'     =>  CUSTOM_TAXONOMY_POPULAR_ITEM,
            'all_items'         =>  CUSTOM_TAXONOMY_ALL_ITEMS,
            'parent_item'       =>  CUSTOM_TAXONOMY_PARENT_ITEM,
            'parent_item_colon' =>  CUSTOM_TAXONOMY_PARENT_ITEM_COLON,
            'edit_item'         =>  CUSTOM_TAXONOMY_EDIT_ITEM,
            'update_item'       =>  CUSTOM_TAXONOMY_UPDATE_ITEM,
            'add_new_item'      =>  CUSTOM_TAXONOMY_ADD_NEW_ITEM,
            'new_item_name'     =>  CUSTOM_TAXONOMY_NEW_ITEM_NAME   ),
            'public'            => true,
            'show_ui'           => true,
            "rewrite"           => true )

        array(  CUSTOM_POST_TYPE1   ),
        array(  "hierarchical"      => false,
            "label"             => CUSTOM_TAG_LABEL,
            'labels'            => array(   'name'              => CUSTOM_TAG_NAME,
            'singular_name'     => CUSTOM_TAG_SINGULAR_NAME,
            'search_items'      => CUSTOM_TAG_SEARCH_ITEMS,
            'popular_items'     => CUSTOM_TAG_POPULAR_ITEMS,
            'all_items'         => CUSTOM_TAG_ALL_ITEMS,
            'parent_item'       => CUSTOM_TAG_PARENT_ITEM,
            'parent_item_colon' => CUSTOM_TAG_PARENT_ITEM_COLON,
            'edit_item'         => CUSTOM_TAG_EDIT_ITEM,
            'update_item'       => CUSTOM_TAG_UPDATE_ITEM,
            'add_new_item'      => CUSTOM_TAG_ADD_NEW_ITEM,
            'new_item_name'     => CUSTOM_TAG_NEW_ITEM_NAME ),
            'public'            => true,
            'show_ui'           => true,
            "rewrite"           => true )
        //===============EVENT SECTION END================

share|improve this answer
This is bad to create such a huge amount of constants. Really really bad practice – Pieter Goosen Dec 29 '15 at 8:36

You have several errors with your code. I cleaned up your existing code:

function jcj_club_post_types() {
  $labels = array(
    'name' => __( 'Jazz Clubs' ),
    'singular_name' => __( 'Jazz Club' ),
    'add_new' => __( 'Add New' ),
    'add_new_item' => __( 'Add New Jazz Club' ),
    'edit' => __( 'Edit' ),
    'edit_item' => __( 'Edit Jazz Clubs' ),
    'new_item' => __( 'New Jazz Club' ),
    'view' => __( 'View Jazz Club' ),
    'view_item' => __( 'View Jazz Club' ),
    'search_items' => __( 'Search Jazz Clubs' ),
    'not_found' => __( 'No jazz clubs found' ),
    'not_found_in_trash' => __( 'No jazz clubs found in Trash' ),
    'parent' => __( 'Parent Jazz Club' ),
  $args = array(
    'public' => true,
    'show_ui' => true,
    'publicly_queryable' => true,
    'exclude_from_search' => false,
    'menu_position' => 5,
    'query_var' => true,
    'supports' => array( 'title','editor','comments','revisions','trackbacks','author','excerpt','thumbnail','custom-fields' ),
    'rewrite' => array( 'slug' => 'jazz-clubs-in', 'with_front' => true ),
    'has_archive' => true
  register_post_type( 'jcj_club', $args );
add_action( 'init','jcj_club_post_types' );

Replace your code with the code above and see if it works. Reply back if you have further questions and I'll try to help.


I noticed I left out 'has_archive' => true.

share|improve this answer
thanks, will test and post back Jeremy. cheers – hash Jun 5 '12 at 11:00

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.