A good[ish] website

Web development blog, loads of UI and JavaScript topics

Date archives for WordPress custom post types

Filed under: WordPress

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.

Comments would go here, but the commenting system isn’t ready yet, sorry. Tweet me @hiljaa if you want to make a correction etc.

  • © 2021 Antti Hiljá
  • About
  • Follow me in Twatter → @hiljaa
  • All rights reserved yadda yadda.
  • I can put just about anything here, no one reads the footer anyways.
  • I love u!