Event query recipes

How to query events correctly, reproducing what the plugin's own listings do.

The rule

Query on event_start_date and event_end_date. They are normalized Y-m-d values written on save, precisely so they can be compared and sorted.

The datetime values are for display. Comparing them in a query gives unreliable results.

Upcoming events, in order

This is what the search form does:

$query = new WP_Query( array(
    'post_type'      => 'event',
    'post_status'    => 'publish',
    'posts_per_page' => 12,
    'orderby'        => 'event_start_date',
    'order'          => 'ASC',
    'meta_query'     => array(
        array(
            'key'     => 'event_end_date',
            'value'   => date( 'Y-m-d' ),
            'compare' => '>=',
            'type'    => 'DATE',
        ),
    ),
) );

Note the comparison is on the end date: an event that has started but not finished is still upcoming.

Past events

'meta_query' => array(
    array(
        'key'     => 'event_end_date',
        'value'   => date( 'Y-m-d' ),
        'compare' => '<',
        'type'    => 'DATE',
    ),
),
'orderby' => 'event_start_date',
'order'   => 'DESC',

Most recent first, which is what a past-events listing wants.

Events running on a specific date

The date picker's logic — events that started on or before the date and end on or after it, so a multi-day event is found on every one of its days:

'meta_query' => array(
    array(
        'key'     => 'event_start_date',
        'value'   => $date,
        'compare' => '<=',
        'type'    => 'DATE',
    ),
    array(
        'key'     => 'event_end_date',
        'value'   => $date,
        'compare' => '>=',
        'type'    => 'DATE',
    ),
),

Events in a month

'meta_query' => array(
    array(
        'key'     => 'event_start_date',
        'value'   => "$year-$month-01",
        'compare' => '>=',
        'type'    => 'DATE',
    ),
    array(
        'key'     => 'event_end_date',
        'value'   => date( 'Y-m-t', strtotime( "$year-$month-01" ) ),
        'compare' => '<=',
        'type'    => 'DATE',
    ),
),

Filtering by taxonomy

'tax_query' => array(
    array(
        'taxonomy'         => 'event_category',
        'field'            => 'slug',
        'terms'            => array( 'concerts' ),
        'include_children' => true,
    ),
),

Both event taxonomies are hierarchical, so include children unless you deliberately want the parent alone.

Events with places remaining

There is no stored "remaining" value; it is derived from the capacity and the registrations. Compute it:

$meta       = get_post_meta( $event_id, 'pom', true );
$capacity   = (int) ( $meta['available_space'] ?? 0 );
$assistants = get_post_meta( $event_id, 'event_assistants', true ) ?: array();

$unlimited = 0 === $capacity;
$remaining = $unlimited ? null : max( 0, $capacity - count( $assistants ) );

A capacity of zero means unlimited. Do not try to express "has places" as a meta query.

Events a user is registered for

$events = get_user_meta( $user_id, 'attended_events', true ) ?: array();

Or, from the event's side, inspect its event_assistants map, which is keyed by user ID.

Performance

  • Do not use posts_per_page => -1 on a public page. Both listings paginate for a reason.
  • Date meta queries are string comparisons on normalized values, which is why the derived keys exist.
  • Counting registrations means loading the map. For a listing showing availability across many events, expect one meta read per event and cache the result.

Always guard the post type

if ( ! post_type_exists( 'event' ) ) {
    return; // Not the POM theme, or too early.
}