Having a little problem with URL rewrites.

Trying to get the following URL to display all the sermons in 2014


Currently it is looking for a sermon called 2014

function caffeine_rewite_rules(){
    add_rewrite_rule("sermons/([0-9]{4})/?$", 'index.php post_type=ctc_sermon&year=$matches[1]', "top");
add_action( 'plugins_loaded', 'caffeine_rewite_rules');

This "should" add a new rewrite rule to the top of the rewrite array that matches for sermons/year but it's never matching. Any thoughts?

  • It looks like your string is broken. index.php post_type=... has a space instead of a ?. See my answer for a full fledged solution. – Eric Holmes Apr 4 '14 at 19:33

I believe this should handle it for you. Including monthly and daily. It also catches if you have defined a different rewrite slug than your custom post type slug.

/* This function handles setting up Date archive rewrite rules for
 * ANY custom post type - You pass the CPT, and it will use the
 * re-written slug if applicable.
function eh_generate_date_archives( $cpt ) {
    global $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 ) {
        $slug_archive = $post_type->rewrite['slug'] ? $post_type->rewrite['slug'] : $post_type->name;
    $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;

Important Amendment!

I'm leaving this post here because someone else is bound to think of this and stumble across it! As pointed out in the comment below, this will grab the current year, and it will change all the urls when the permalinks refresh. That is not what I'm going for of course! So that's why this method below is NOT a good idea. Thanks Milo for the heads up. I didn't even think of that.

I found a way to do this that is so simple, I'm a little afraid it must be incredibly wrong otherwise it wouldn't have been overlooked by so many. I know this is an old post, but I found it today while searching for my own answer to this question.

Similarly, I have crafted my own post types.

In the arguments of my post type (in this case, events), I use the rewrite made available to custom post types. Prior to the custom post type, I added a variable for the year.

$year = date("Y");

  $events_labels = array(
    'name'                => _x( 'Events', 'Post Type General Name', 'reaction' ),
    'singular_name'       => _x( 'Event', 'Post Type Singular Name', 'reaction' ),
    'menu_name'           => __( 'Events', 'reaction' ),
    'name_admin_bar'      => __( 'Events', 'reaction' ),
    'parent_item_colon'   => __( 'Parent:', 'reaction' ),
    'all_items'           => __( 'All Events', 'reaction' ),
    'add_new_item'        => __( 'Add New Event', 'reaction' ),
    'add_new'             => __( 'Add New', 'reaction' ),
    'new_item'            => __( 'New', 'reaction' ),
    'edit_item'           => __( 'Edit', 'reaction' ),
    'update_item'         => __( 'Update', 'reaction' ),
    'view_item'           => __( 'View', 'reaction' ),
    'search_items'        => __( 'Search Events', 'reaction' ),
    'not_found'           => __( 'No Events found', 'reaction' ),
    'not_found_in_trash'  => __( 'No Events found in Trash', 'reaction' ),

  $events_args = array(
    'label'               => __( 'events', 'reaction' ),
    'description'         => __( 'YHQ Events', 'reaction' ),
    'labels'              => $events_labels,
    'supports'            => array( 'title', 'editor', 'author', 'thumbnail', 'revisions', ),
    'hierarchical'        => false,
    'public'              => true,
    'show_ui'             => true,
    'show_in_menu'        => true,
    'menu_position'       => 5,
    'menu_icon'           => 'dashicons-calendar',
    'show_in_admin_bar'   => true,
    'show_in_nav_menus'   => true,
    'can_export'          => true,
    'has_archive'         => false,
    'exclude_from_search' => false,
    'publicly_queryable'  => true,
    'rewrite'            => array( 'slug' => 'events/item/' . $year . ''),
    'capability_type'     => 'page',

You can see my rewrite array for the slug includes an extra /item/ path (not necessary, just included since I needed to do a little more customizing for our purposes and it could help someone else) and then my variable for the year.

This was so simple, I am sort of waiting for the other shoe to drop! If anyone knows of why this is a terrible idea, please let me know.

  • 1
    You're just inserting the current year as a static element within the permalink. Every time permalinks are flushed, all posts will assume the current year regardless of what year they were actually published, and any old incoming links with a different year will 404. – Milo Nov 8 '16 at 2:29

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.