Upcoming events archive with month grid
Order an event archive by date, hide past events and render a month calendar grid that groups events by day.
WordPressLayoutseventscalendarwp-queryarchive
Code
<?php
/**
* Event archive query + month grid.
*
* Assumes a post type "acme_event" with a "Y-m-d" meta key "acme_event_date".
*
* @package Acme_Theme
*/
defined( 'ABSPATH' ) || exit;
// 1. Archive: upcoming first, optional ?month=YYYY-MM filter.
function acme_events_query( WP_Query $query ): void {
if ( is_admin() || ! $query->is_main_query() || ! $query->is_post_type_archive( 'acme_event' ) ) {
return;
}
$month = isset( $_GET['month'] ) ? sanitize_text_field( wp_unslash( $_GET['month'] ) ) : '';
if ( preg_match( '/^\d{4}-(0[1-9]|1[0-2])$/', $month ) ) {
$compare = array( 'key' => 'acme_event_date', 'value' => array( "$month-01", "$month-31" ), 'compare' => 'BETWEEN', 'type' => 'DATE' );
} else {
$compare = array( 'key' => 'acme_event_date', 'value' => wp_date( 'Y-m-d' ), 'compare' => '>=', 'type' => 'DATE' );
}
$query->set( 'meta_query', array( $compare ) );
$query->set( 'meta_key', 'acme_event_date' );
$query->set( 'orderby', 'meta_value' );
$query->set( 'order', 'ASC' );
$query->set( 'posts_per_page', 50 );
}
add_action( 'pre_get_posts', 'acme_events_query' );
// 2. Month grid: returns HTML for a Monday-first calendar.
function acme_events_month_grid( string $month ): string {
$first = DateTimeImmutable::createFromFormat( '!Y-m', $month ) ?: new DateTimeImmutable( 'first day of this month' );
$days = (int) $first->format( 't' );
$pad = (int) $first->format( 'N' ) - 1;
$by_day = array();
$events = get_posts(
array(
'post_type' => 'acme_event',
'posts_per_page' => 100,
'meta_key' => 'acme_event_date',
'meta_query' => array( array( 'key' => 'acme_event_date', 'value' => array( $first->format( 'Y-m-01' ), $first->format( 'Y-m-t' ) ), 'compare' => 'BETWEEN', 'type' => 'DATE' ) ),
)
);
foreach ( $events as $event ) {
$day = (int) substr( (string) get_post_meta( $event->ID, 'acme_event_date', true ), 8, 2 );
$by_day[ $day ][] = $event;
}
$html = '<table class="acme-cal"><caption>' . esc_html( wp_date( 'F Y', $first->getTimestamp() ) ) . '</caption><tr>';
foreach ( array( 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun' ) as $label ) {
$html .= '<th scope="col">' . esc_html( $label ) . '</th>';
}
$html .= '</tr><tr>' . str_repeat( '<td></td>', $pad );
for ( $d = 1; $d <= $days; $d++ ) {
if ( 0 === ( $pad + $d - 1 ) % 7 && $d > 1 ) {
$html .= '</tr><tr>';
}
$html .= '<td><span class="acme-cal__n">' . $d . '</span>';
foreach ( $by_day[ $d ] ?? array() as $event ) {
$html .= '<a href="' . esc_url( get_permalink( $event ) ) . '">' . esc_html( get_the_title( $event ) ) . '</a>';
}
$html .= '</td>';
}
return $html . '</tr></table>';
}
How it works
What it solves
An event list sorted by publish date is useless: visitors want what is coming next, or what is on in a chosen month. This snippet queries by an event date meta field and draws a calendar grid from the same data.
How it works
pre_get_posts rewrites only the main query of the event archive. It sorts by a DATE-typed meta value and either shows everything from today forward or, when a validated ?month=YYYY-MM is present, the events within that month. The grid function builds a Monday-first table, using the weekday number of the first day as left padding, and drops each event link into its day cell.
Everything printed is escaped at the point of output.
Adapting it
Store dates as Y-m-d text so MySQL can compare them as dates; datetime meta needs a different type. Add previous and next month links that rebuild ?month=. For recurring events, expand occurrences into a transient rather than into the query. Colour days that contain events using a class on the cell.
Pitfalls
- Hard-coding
-31works withBETWEENon dates but usetfrom DateTime when you need the real last day. - Meta queries are slow on huge sites; index by a custom table if you pass thousands of events.
- Timezones: compare against
wp_date(), notdate(). - Validate the month parameter before using it, as shown.
How to use it
Save as inc/events.php and require it from functions.php.
Register an acme_event post type and a date field saving Y-m-d to acme_event_date.
In a template, print echo acme_events_month_grid( wp_date( 'Y-m' ) ); and loop the archive as usual.
Licence: GPL-2.0-or-later. This snippet comes from WordPress code and is GPL-2.0-or-later.