Create a clean URL for your custom post type’s category archives


WordPress gained great functionality by adding custom post types and one great feature of custom post types is that it can share the same categories as regular posts. However while you can view an archive of posts by category you can’t do so with a custom post type without using an ugly URL.

Let’s say you have a custom post type, gallery and a category, portraits. To view an archive of galleries in the portrait category you could do it with two URLs.

/gallery/?category_name=portraits or /category/portraits/?post_type=gallery

While this works it’s not very pretty. So here’s how to rewrite that first URL to something cleaner.

/* Can be added to functions.php */
add_filter( 'rewrite_rules_array','my_insert_rewrite_rules' );
add_filter( 'query_vars','my_insert_query_vars' );
add_action( 'wp_loaded','my_flush_rules' );

// flush_rules() if our rules are not yet included
function my_flush_rules() {
$rules = get_option( 'rewrite_rules' );
if ( ! isset( $rules['gallery/category/(.*/?)$'] ) ) {
global $wp_rewrite;
$wp_rewrite->flush_rules();
}
}

// Adding a new rule
function my_insert_rewrite_rules( $rules ) {
$newrules = array();
$newrules['gallery/category/(.*/?)$'] = 'index.php?post_type=gallery&my_gallery_cat=$matches[1]';
return $newrules + $rules;
}

// Adding the my_gallery_cat var so that WP recognizes it
function my_insert_query_vars( $vars ) {
array_push($vars, 'my_gallery_cat');
return $vars;
}

If we just used the category_name variable then WordPress would try to override our custom URL, so instead we’ll use a made up variable my_gallery_cat. To use this variable create a archive template for your custom post type, in this case archive-gallery.php, and modify the query it to accept changes to the category:

global $wp_query;
$args = array_merge( $wp_query->query, array(
'category_name' => $my_gallery_cat
));
query_posts( $args );

The final result is a nice URL for the category archive: /gallery/category/portraits/

You may have to flush your rewrite rules by going to Settings -> Permalinks, then just hit Save Changes.

Update 2012-01-06
Forgot to mention that you may need to flush your rewrite rules, thanks e01.

Update 2012-01-06
Added add_filter(‘query_vars’, ‘my_insert_query_vars’ );, thanks Arnaud


7 Comments

  1. Ouh.. doesn’t work for me..
    my post type category url mysitecom/blog-category/blabla

    query, array(
    'post_type' => 'blog', 
    'category_name' => $blog-category,
    'posts_per_page' => 2 
    ));
    query_posts( $arg );
    ?>
    

    and dispalying not found…


    • You have to flush the rules. You can easy do this as go to admin-panel -> Settings -> permalinks


  2. Thanks for this post.

    In your code, I think you forgot to register dmu_insert_query_vars as a filter:

    add_filter('query_vars', 'dmu_insert_query_vars' );


  3. I know that this may be a very simple question but how would this work if I have two seperate posttypes that are listed in multiple categories? I am a novice with post types and php.

    Please help. Thanks!


  4. If your adding your own query var that you then use to pass something to query_posts – thats going to add an extra query to the database when its completely unnecessary.

    Your rewrite rule should look lik this:
    ['gallery/category/(.*/?)$'] = ‘index.php?post_type=gallery&category_name=$ma’

    By just using the correct var to begin with you avoid the need to add the custom var, and avoid the need to later do the array merge and subsequent query_posts, which as I stated would trigger an unnecessary extra hit to the database.


Speak up!