How to sort CPT by custom meta value (date), and return posts month by month

This isn’t complete copy/paste code, but hopefully it’s understandable enough to get you started.

First step is to register your post type and add a rewrite rule to handle years/months. This will give you single events at event/post-name/, your post type archive at calendar, and handle incoming requests for calendar/yyyy/mm/. Make sure to visit your Settings > Permalinks page after this is added to flush the rewrite rules.

function wpa88173_calendar_post_type() {

    // just the important bits shown here
    $args = array(
        'rewrite' => array( 'slug' => 'event' )
        'has_archive' => 'calendar',
    );

    register_post_type( 'calendar', $args );

    add_rewrite_rule(
        '^calendar/([0-9]{4})/([0-9]{2})/?',
        'index.php?post_type=calendar&calendar_year=$matches[1]&calendar_month=$matches[2]',
        'top'
    );

}
add_action( 'init', 'wpa88173_calendar_post_type' );

Next step is to add the calendar_year and calendar_month query vars so WordPress adds them to the array of query vars when the incoming requests are parsed.

function wpa88173_calendar_query_vars( $query_vars ) {
    $query_vars[] = 'calendar_year';
    $query_vars[] = 'calendar_month';
    return $query_vars;
}
add_filter('query_vars', 'wpa88173_calendar_query_vars' );

The next step is to add an action to pre_get_posts, which checks if it’s the calendar post type archive, fetches the year/month or sets it to the current year/month, then modifies the query with meta_query parameters to load the requested year/month. See WP_Query for more info on meta queries. This assumes a date format of yyyymmdd.

function wpa88173_calendar_query( $query ) {
    // is it a post type archive?
    if( ! $query->is_post_type_archive( 'calendar' ) )
        return;

    // is it the main query and not an admin page?      
    if( $query->is_main_query()
        && ! is_admin() ) {

        // check if year/month was set via the URI, or set it to current year/month
        ( ! empty( $query->query_vars['calendar_year'] ) ) ? $query_year = $query->query_vars['calendar_year'] : $query_year = date('Y');
        ( ! empty( $query->query_vars['calendar_month'] ) ) ? $query_month = $query->query_vars['calendar_month'] : $query_month = date('m');

        // meta_query parameters for events between start and end dates
        $date_start = $query_year . $query_month . '01';
        $date_end = $query_year . $query_month . '31';
        $meta_query = array(
            array(
                'key' => 'event_date',
                'value' => array( $date_start, $date_end ),
                'compare' => 'BETWEEN',
                'type' => 'NUMERIC'
            )
        );

        // modify the query
        $query->set( 'meta_key', 'event_date' );
        $query->set( 'orderby', 'meta_value_num' );
        $query->set( 'order', 'ASC' );
        $query->set( 'meta_query', $meta_query );

    }

}
add_action( 'pre_get_posts', 'wpa88173_calendar_query' );

The last step will be for you to build the calendar in your template and create the next/previous links to page through months. You can get the queried year/month in the template via get_query_var.

EDIT – Here’s an example of building the links with plain ol’ math

( '' == get_query_var( 'calendar_month' ) ) ? $this_month = date( 'n' ) : $this_month = ltrim( get_query_var( 'calendar_month' ), '0' );
( '' == get_query_var( 'calendar_year' ) ) ? $this_year = date( 'Y' ) : $this_year = get_query_var( 'calendar_year' );

if( 1 == $this_month ):
    $next_month = 2;
    $prev_month = 12;
    $next_year = $this_year;
    $prev_year = $this_year - 1;
elseif( 12 == $this_month ):
    $next_month = 1;
    $prev_month = 11;
    $next_year = $this_year + 1;
    $prev_year = $this_year;
else:
    $next_month = $this_month + 1;
    $prev_month = $this_month - 1;
    $next_year = $this_year;
    $prev_year = $this_year;
endif;

$next_month = str_pad( $next_month , 2, '0', STR_PAD_LEFT );
$prev_month = str_pad( $prev_month , 2, '0', STR_PAD_LEFT );

echo 'next month: /calendar/' . $next_year . "https://wordpress.stackexchange.com/" . $next_month . "https://wordpress.stackexchange.com/";
echo 'previous month: /calendar/' . $prev_year . "https://wordpress.stackexchange.com/" . $prev_month . "https://wordpress.stackexchange.com/";

Leave a Comment

tech