Take the 2-minute tour ×
WordPress Development Stack Exchange is a question and answer site for WordPress developers and administrators. It's 100% free, no registration required.

How do you display the archives of a Custom Post Type by Year & Month?

share|improve this question

5 Answers 5

up vote 6 down vote accepted

Yes, you can. All you need is make a filter for wp_get_archives(); so it accepts post_type parameter:

function my_custom_post_type_archive_where($where,$args){  
    $post_type  = isset($args['post_type'])  ? $args['post_type']  : 'post';  
    $where = "WHERE post_type = '$post_type' AND post_status = 'publish'";
    return $where;  

then call this:

add_filter( 'getarchives_where','my_custom_post_type_archive_where',10,2);

Whenever you want to display archive by custom post type, just pass the post_type args:

$args = array(
    'post_type'    => 'your_custom_post_type',
    'type'         => 'monthly',
    'echo'         => 0
echo '<ul>'.wp_get_archives($args).'</ul>';
share|improve this answer
Did you try this solution? It correctly fetches the list of months with a post from your CPT and the number of posts, but the links are useless. Clicking them takes you to the month for the whole site, not for the CPT. –  jeremyclarke Sep 21 '12 at 16:12
+1 why isn't this in core? –  BandonRandon Feb 17 '13 at 23:48
After some digging (see Tom Nowell's answer below), I gave up on monthly archives for custom post types. Instead, I used a post category, and changed my permalink structure to /%category%/%year%/%monthnum%/%postname%/. Then, it might be possible using a hook similar to above, to modify the links to start with /%category%/ instead of just the date. –  Kevin C. Mar 22 '13 at 22:31
Question on this. This creates urls like: mysite.com/2013/04 but this leads to a 404. The custom post type is available at: mysite.com/cats which makes me think mysite.com/cats/2013/04 would be what the link should be, but this also resolves to a 404. How do you get the archive links to work? –  gaoshan88 Apr 11 '13 at 5:56
Even better, there is finally plugin to deal with this missing functionality in WordPress. It was created by a core contributor trying to fix this issue in the core. The plugin is provided to get us through until the issue is properly addressed in the core. wordpress.org/plugins/archives-for-custom-post-types –  Trevor Jul 9 at 18:01

You don't, the official line from the Wordpress developers was that custom post types weren't intended to do the job of normal ordinary posts, and that if you need post archives of dates etc, then you're not doing things correctly, and you're better off using post formats etc..

Custom post types are intended for web applications etc, whereas doing something such as setting up a custom post type that acts as a secondary or parallel blog with a different name, e.g. blog vs news, with the same abilities, is not what the feature was intended for, and would mean other technical issues arising from its implementation.

If you are still insistent on this, and simply using custom taxonomies and post formats is not enough, you could add rewrite rules in functions.php and redirect year/month archives in certain URLs to the post archive page, and then check on the custom post archive page if you've specified variables in your rewrite rules and load a different template, making sure in your rewrite rules to set the appropriate values.

share|improve this answer
It does seem a bit odd that they would only go so far with this functionality. Can you give me an example of how custom posts should be used? –  Dan Lee Nov 17 '11 at 13:02
Custom posts should be used for anything that isn't covered by the scope of pages and blog posts ( or blog posts with a different name but work the same, e.g. articles/news/diary/etc ) Examples of correct uses of custom posts include: events, menus, locations, forms, logs, etc –  Tom J Nowell Nov 17 '11 at 13:05
Custom posts are basically the means of producing web apps, they're not the means of duplicating the post menu in the backend for easier editing ( and such a use would make wordpress much slower and be more of a task than you realise to do ) –  Tom J Nowell Nov 17 '11 at 13:12
UUUUUgh. This is the correct answer to the question tragically. I can't believe that the explanation above is based on "we shouldn't have date URLs for CPTs" though, it is almost definitely "Date URLs for CPTs are too complicated" that is driving the actual decision not to implement. CLEARLY there are cases where people would want date archives for a custom post type, you can't make that obvious desire dissapear by pointing out post formats. –  jeremyclarke Sep 21 '12 at 17:36

Not enough reputation to add this to taiken's answer sorry.

However wanted to add that his answer did work for me, however the links were in the 'localhost/date/2010' format. Whereas I needed 'localhost/postslug/2010' format. I was able to fix this by using a string replace on the output of wp_get_archives.

So depending on how your permalinks are set this code will fix the 404 problem and redirect the links to the custom post type permalink structure:

$yearly_archive = wp_get_archives(array( 'type' => 'yearly', 'post_type' => '<your post type name>', 'echo' => '0') );
$blog_url = get_bloginfo('url');
echo str_replace(($blog_url . '/date'), ($blog_url . '<your post type slug>'),$yearly_archive);
share|improve this answer

Despite claims stating otherwise, you can definitely implement fully functional Year/Month archives for Custom Post Types. It should be part of WordPress, but for now just install "Custom Post Type Archives" plugin - http://wordpress.org/extend/plugins/custom-post-type-archives/.

This plugin creates a function called wp_get_post_type_archives() which works the same as wp_get_archives() but allows you use Custom Post Types.

There is one problem with the plugin in that it seems to not work with clean permalinks. So you need to modify the plugin with this fix below or see this reference link.

Replace this code in the plugin file "post-type-archives.php" (starting at line 164):

$custom_rules["$url_base($post_type)/([0-9]+)/([0-9]{1,2})/([0-9]{1,2})/?$"] = // enable listing by day
            "index.php?post_type_index=1&post_type=$post_type_slug&year=" . $wp_rewrite->preg_index(2) . '&monthnum=' . $wp_rewrite->preg_index(3) . '&day=' . $wp_rewrite->preg_index(4);
        $custom_rules["$url_base($post_type)/([0-9]+)/([0-9]{1,2})/?$"] = // enabled listing by month
            "index.php?post_type_index=1&post_type=$post_type_slug&year=" . $wp_rewrite->preg_index(2) . '&monthnum=' . $wp_rewrite->preg_index(3);
        $custom_rules["$url_base($post_type)/([0-9]+/?$)"] = // enable listing by year
            "index.php?post_type_index=1&post_type=$post_type_slug&year=" . $wp_rewrite->preg_index(2);

With this code below:

$custom_rules["$url_base($post_type)/([0-9]{1,4})/([0-9]{1,2})/([0-9]{1,2})/page/([0-9]{1,})/?$"] = // ### FIX: enable listing by day + paging
            "index.php?post_type_index=1&post_type=$post_type_slug&year=" . $wp_rewrite->preg_index(2) . '&monthnum=' . $wp_rewrite->preg_index(3) . '&day=' . $wp_rewrite->preg_index(4) . "&paged=" . $wp_rewrite->preg_index(5);
        $custom_rules["$url_base($post_type)/([0-9]{1,4})/([0-9]{1,2})/([0-9]{1,2})/?$"] = // enable listing by day
            "index.php?post_type_index=1&post_type=$post_type_slug&year=" . $wp_rewrite->preg_index(2) . '&monthnum=' . $wp_rewrite->preg_index(3) . '&day=' . $wp_rewrite->preg_index(4);
        $custom_rules["$url_base($post_type)/([0-9]{1,4})/([0-9]{1,2})/page/([0-9]{1,})/?$"] = // ### FIX: enable listing by month + paging
            "index.php?post_type_index=1&post_type=$post_type_slug&year=" . $wp_rewrite->preg_index(2) . '&monthnum=' . $wp_rewrite->preg_index(3) . "&paged=" . $wp_rewrite->preg_index(4);
        $custom_rules["$url_base($post_type)/([0-9]{1,4})/([0-9]{1,2})/?$"] = // enabled listing by month
            "index.php?post_type_index=1&post_type=$post_type_slug&year=" . $wp_rewrite->preg_index(2) . '&monthnum=' . $wp_rewrite->preg_index(3);
        $custom_rules["$url_base($post_type)/([0-9]{1,4})/page/([0-9]{1,})/?$"] = // ### FIX: enable listing by year + paging
            "index.php?post_type_index=1&post_type=$post_type_slug&year=" . $wp_rewrite->preg_index(2) . "&paged=" . $wp_rewrite->preg_index(3);
        $custom_rules["$url_base($post_type)/([0-9]{1,4})/?$"] = // enable listing by year
            "index.php?post_type_index=1&post_type=$post_type_slug&year=" . $wp_rewrite->preg_index(2);
share|improve this answer

Can't add to takien's post so here's what I ended up having to do:


add_action('init', 'my_year_archive_rewrites');  
function my_year_archive_rewrites() {   
    add_rewrite_rule('resource/news/([0-9]{4})/page/?([0-9]{1,})/?', 'index.php?post_type=news&year=$matches[1]&paged=$matches[2]', 'top');
    add_rewrite_rule('resource/news/([0-9]{4})/?', 'index.php?post_type=news&year=$matches[1]', 'top');

add_filter('getarchives_where', 'my_custom_post_type_archive_where', 10, 2);
function my_custom_post_type_archive_where($where,$args){  
    $post_type = isset($args['post_type']) ? $args['post_type'] : 'post';  
    return "WHERE post_type = '$post_type' AND post_status = 'publish'";

add_filter('year_link', 'my_year_link');
function my_year_link($link) {
    global $wp_rewrite;

    if(true) { // however you determine what archive you want
        $link = str_replace($wp_rewrite->front, '/resource/news/', $link);

    return $link;

Calling wp_get_archives()

wp_get_archives(array('post_type'=>'news', 'type'=>'yearly'));
share|improve this answer
See his is your first answer. Just a tip when answering questions, don't just add code or for that matter links. Although your code might work, it is always nice to know what your code does and why it should work. Otherwise your answer is nicely formatted. +1 for that –  Pieter Goosen Apr 17 at 16:28

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.