WordPress How To: Monthly,Yearly and Daily Archives for Custom Post Types

For a plugin I was creating I needed Yearly and Monthly archives with my own slug. Even after setting the rewrite rules it was not working. After much debugging and several hours later I finally figured it out. I’ll explain how you could do the same bellow.

Spoiler Alert: For the impatient, it was as simple as removing the old rules that are not setting the post type before adding the new rules 🙂

I’m going to assume that you have already familiar with Custom Post types. If you are not read http://codex.wordpress.org/Post_Types and then come back. Most important are Step 5 and Step 6. Other steps are typical for any plugin or theme using custom post types.

Step 1: Register the post type
Read more about registering custom post types at

register_post_type( 'my_class',
'public' => true,
'has_archive' => true,

Step 2: Add rewrite tags and permalink structure

$event_structure = '/events/%year%/%monthnum%/%my_class%';
$wp_rewrite->add_rewrite_tag("%my_class%", '(.+?)', "my_class=");
$wp_rewrite->add_permastruct('my_class', $event_structure, false);

Step 3: Add post_type_link and rewrite_rules_array filters

add_filter('rewrite_rules_array', 'my_class_add_rewrite_rules');
add_filter('post_type_link', 'my_class_post_type_link', 10, 3);

Step 4: Return a proper permalink

function my_class_post_type_link($permalink, $post_id, $leavename) {
$post = get_post($post_id);

$rewritecode = array(

if ($post->post_type == 'my_class' && '' != $permalink) {

$ptype = get_post_type_object($post->post_type);

$start = time();
$end = time();

$meta = get_post_custom($post->ID);
// This is where I store when the class starts
if (isset($meta["my_class_start"]) && isset($meta["my_class_start"][0])) {
$start = strtotime($meta["my_class_start"][0]);

$year = date('Y', $start);
$month = date('m', $end);

$rewritereplace = array(
($post->post_name == "")?$post->id:$post->post_name,
$permalink = str_replace($rewritecode, $rewritereplace, $permalink);
} else {
// if they're not using the fancy permalink option

return $permalink;

Step 5: Add rewrite rules

function my_class_add_rewrite_rules($rules){
$new_rules = array();

// This is the important bit, unsetting the rules

$new_rules['classes/([0-9]{4})/?$'] = 'index.php?year=$matches[1]&post_type=incsub_event';
$new_rules['classes/([0-9]{4})/([0-9]{1,2})/?$'] = 'index.php?year=$matches[1]&monthnum=$matches[2]&post_type=incsub_event';
$new_rules['classes/([0-9]{4})/([0-9]{2})/(.+?)/?$'] = 'index.php?year=$matches[1]&monthnum=$matches[2]&incsub_event=$matches[3]';

return array_merge($new_rules, $rules);

Step 6: Don’t forget to flush

add_action('option_rewrite_rules', 'my_class_check_rewrite_rules');
function my_class_check_rewrite_rules($value) {
global $wp_rewrite;

//prevent an infinite loop
if ( ! post_type_exists( 'incsub_event' ) )

if (!is_array($value))
$value = array();

$array_key = 'events/([0-9]{4})/?$';
if ( !array_key_exists($array_key, $value) ) {
$array_key = 'events/([0-9]{4})/([0-9]{1,2})/?$';
if ( !array_key_exists($array_key, $value) ) {
$array_key = 'events/([0-9]{4})/([0-9]{1,2})/(.+?)/?$';
if ( !array_key_exists($array_key, $value) ) {

That’s it. Most important steps are Step 5 and Step 6

If you enjoyed this post, make sure you subscribe to my RSS feed!

Leave a Comment