Date archives for WordPress custom post types

On this post we'll look how to make rewrite rules for the custom post type date archives and how to modify the output of wp_get_archives() to facilitate the new date archives.

This is one of the things that are really easy to do with built in post types, but requires extra work when custom post type is in use.

If you use wp_get_archives() to get a date archive, it only gets them for the “Posts” post type, and not for custom post types (CPTs). This article deals with three things:

  1. How to apply rewrite rules so that CPTs have date archive.
  2. How to get a custom post type included in output of wp_get_archives().
  3. How to make wp_get_archives() output right kinda URLs e.g.


The custom post type needs to have 'has_archive' => true. More here.

Making date archive for custom post types

Here’s two functions that get the rewrite rules sorted out for you, these go into functions.php. They’re lifted this from here, and I’ve improved it a bit, see the comments for explanation. Remember to change your custom post type in $rules in the first function.

 * Custom post type specific rewrite rules
 * @return wp_rewrite Rewrite rules handled by WordPress
function cpt_rewrite_rules($wp_rewrite)
    // Here we're hardcoding the CPT in, article in this case
    $rules = cpt_generate_date_archives('article', $wp_rewrite);
    $wp_rewrite->rules = $rules + $wp_rewrite->rules;
    return $wp_rewrite;
add_action('generate_rewrite_rules', 'cpt_rewrite_rules');

 * Generate date archive rewrite rules for a given custom post type
 * @param  string $cpt slug of the custom post type
 * @return rules       returns a set of rewrite rules for WordPress to handle
function cpt_generate_date_archives($cpt, $wp_rewrite)
    $rules = array();

    $post_type = get_post_type_object($cpt);
    $slug_archive = $post_type->has_archive;
    if ($slug_archive === false) {
        return $rules;
    if ($slug_archive === true) {
        // Here's my edit to the original function, let's pick up
        // custom slug from the post type object if user has
        // specified one.
        $slug_archive = $post_type->rewrite['slug'];

    $dates = array(
            'rule' => "([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})",
            'vars' => array('year', 'monthnum', 'day')
            'rule' => "([0-9]{4})/([0-9]{1,2})",
            'vars' => array('year', 'monthnum')
            'rule' => "([0-9]{4})",
            'vars' => array('year')

    foreach ($dates as $data) {
        $query = 'index.php?post_type='.$cpt;
        $rule = $slug_archive.'/'.$data['rule'];

        $i = 1;
        foreach ($data['vars'] as $var) {
            $query.= '&'.$var.'='.$wp_rewrite->preg_index($i);

        $rules[$rule."/?$"] = $query;
        $rules[$rule."/feed/(feed|rdf|rss|rss2|atom)/?$"] = $query."&feed=".$wp_rewrite->preg_index($i);
        $rules[$rule."/(feed|rdf|rss|rss2|atom)/?$"] = $query."&feed=".$wp_rewrite->preg_index($i);
        $rules[$rule."/page/([0-9]{1,})/?$"] = $query."&paged=".$wp_rewrite->preg_index($i);
    return $rules;

NOTE: permalinks needed to be flashed after this, meaning that go to Settings > Permalinks and click the “Save Changes” button.

Now you should be able to access monthly archives from and so on. You’d use the archive-{cpt-name}.pgp template to display the entries.

Make wp_get_archives() work with CPTs

Next, we need to get links to the archive pages, we can modify wp_get_archives() to do that. Let’s use the getarchives_where filter:

function example_getarchives_where($where)
    return str_replace("WHERE post_type = 'post'", "WHERE post_type IN ('article')", $where);
add_filter('getarchives_where', 'example_getarchives_where');

Note the second argument to str_replace, there we can specify what post types will be included. If we want to throw more post types into the mix. The following gets Posts and Articles:

WHERE post_type IN ('post', 'article')

See the getarchives_where source code here, that might open things up a bit.

Now the wp_get_archives() outputs posts from the right post type, but URLs it emits are still wrong: we want One more function is needed.

Make wp_get_archives() give right URLs

This could be done right in a template also, but I prefer keeping templates clean and abstracting all the functions in functions.php. The following is just a wrapper around wp_get_archives(). It it replaces with{cpturl}. See comments for more info:

 * Get archives to custom post type
 * @param  string $cpt The wanted custom post type
 * @return array       A list of links to date archives
function cpt_wp_get_archives($cpt)
    // Configure the output
    $args = array(
        'format'          => 'custom',
        'before'          => '<li class="post-list__item post-list__item--archive">',
        'after'           => '</li>',
        'echo'            => 0,
        'show_post_count' => true
    // Get the post type objest
    $post_type_obj = get_post_type_object($cpt);
    // Slug might not be the cpt name, it might have custom slug, so get it
    $post_type_slug = $post_type_obj->rewrite['slug'];
    // Domain of the current site
    $host = $_SERVER['HTTP_HOST'];
    // Replace `domain.tld` with `domain.tdl/{cpt-slug}`
    $output = str_replace($host, "$host/$post_type_slug", wp_get_archives($args));

    return $output;

Then use it:

echo cpt_wp_get_archives('custom-post-type-name');


This was meant to be one of those quick things, but ended up being a wild journey deep into the gut of WP. And don’t forget to flash your permalinks.


  • lijo says:


  • Asoode says:

    You Sir, deserve a medal of honor for this post :)

    Thank you a lot.

  • Stacy says:

    This is successfully handling my rewrite structure, but I’m not having any luck getting it to spit out the actual list of months for the user to select from. I have everything above in functions.php (changing ‘article’ to ‘blog’ as per my CPT name), with the exception of
    which I’ve added to my template file. Where am I going wrong?

  • Stacy says:

    –whoops it strips php, was trying to say that echo cpt_wp_get_archives(‘blog’); is in my template file (wrapped in php)

  • Dmitry says:

    Its so awersome, other numerous method doesn’t work, but this its something beautiful.
    Thank you very much.

  • Darren says:

    Great piece of code, well done

    How do I apply this function to 2 different custom post types.

    I have tried duplicating the function and changing cpt name but it throws an error



  • Beee says:

    Great post, but I get an error on line 18 in

    This is the error: Notice: Trying to get property of non-object in filename.php on line 18

  • Beee says:

    Never mind my comment :) My CPT was not defined anymore, hence the error….

  • Rajani says:


  • Aaron Smith says:

    Wow, just wow. Thank you for assembling this. It saved my butt, if I could donate 10$ to you for this, I would.

  • Gary says:

    It works beautifully to get a list of months and years, but why are the URLs giving me a 404 Page Not Found error? I’m puling my hair out for hours Thanks for any help you can give. => ‘404’;


  • Tom says:

    How would I use this code alongside the non custom ‘post’ post type.

    I’ve added the code and it works great for my custom post type but it’s also affecting my standard ‘posts’.


  • Rasso says:

    OMG, this is just an amazing piece of work here, many thanks! Just in case, anyone was wondering how to make it work with multiple custom post types:

    function cpt_rewrite_rules($wp_rewrite) {

    $cpts = array('music_event', 'film_event', 'dinner_event');

    foreach( $cpts as $cpt ) {
    $rules = generate_date_and_search_archives($cpt, $wp_rewrite);
    $wp_rewrite->rules = array_merge( $rules, $wp_rewrite->rules );

    return $wp_rewrite;

  • Thrasher says:

    This is a masterpiece.

    Thank you from Italy

Club-Mate, the beverage →