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

Categories: ,

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;
// 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 an archive template for your custom post type, in this case archive-gallery.php, and modify the query 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_var’);, thanks Arnaud

  1. 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.

  2. 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!

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

    • I sure did, thanks!

  4. 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

      • Thanks. I just added this to the tutorial.

Add Yours
Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Contact Me

How can I help you?

© 2024 Seth Stevenson Marketing. All rights reserved.