Updated on 5 Dec 2024
In the comments section of Events grouped by months within years based on event date custom field value tutorial, a member asked:
… A good update for this would be a single loop of just MONTH YEAR with the events under each Month and Year, rather than YEAR, then MONTh then events.
This Pro tutorial is another example of custom advanced Bricks query loop types for showing future posts of event CPT based on a date custom field using three nested query loops.

The Heading element’s text is dynamic and automatically pulls data from the Event Year and Event Month query loop objects.
Similarly, the Post Loop pulls the year and month from the parent queries, passes them to a custom function which returns the IDs of posts from that year and and that month.
Events list:

Output on the front end after implementing the tutorial:

Step 1
Add the following in child theme‘s functions.php (w/o the opening PHP tag) or a code snippets plugin:
<?php
// Add new "Event Years" query loop type option in the dropdown.
add_filter( 'bricks/setup/control_options', function( $control_options ): array {
$control_options['queryTypes']['event_years'] = esc_html__( 'Event Years' );
$control_options['queryTypes']['event_months'] = esc_html__( 'Event Months' );
return $control_options;
} );
// Run new query if option selected.
add_filter( 'bricks/query/run', function( $results, $query_obj ): array {
if ( $query_obj->object_type !== 'event_years' && $query_obj->object_type !== 'event_months' ) return $results;
// if option is selected, run our new query
if ( $query_obj->object_type === 'event_years' ) {
$results = bl_get_event_year_data()['years'];
}
if ( $query_obj->object_type === 'event_months' ) {
// Current looping year
$year = bl_get_loop_object_by_level();
// Associative array with years as keys and array of months (during which there are events) as values
$year_months = bl_get_event_year_data()['year_months'];
// Return the current looping year's months
$results = $year_months[ $year ] ?? [];
}
return $results;
}, 10, 2 );
// Form an array of event years and months data, and return it.
if ( ! function_exists( 'bl_get_event_year_data' ) ) {
function bl_get_event_year_data(): array {
$year_month_post_ids = [];
$year_months = [];
$posts = get_posts([
'post_type' => 'event',
'posts_per_page' => 100, // Set this to a large number
'meta_key' => 'event_date',
'orderby' => 'meta_value_num',
'order' => 'ASC',
'meta_query' => [
[
'key' => 'event_date',
'compare' => 'EXISTS',
'type' => 'DATE',
],
[
'key' => 'event_date',
'compare' => '>=',
'value' => date( 'Ymd' ),
],
],
]);
foreach ( $posts as $post ) {
// Store the event_date post meta value
$event_date = get_post_meta( $post->ID, 'event_date', true );
// Store current event's year in this format: 2024
$year = wp_date( 'Y', strtotime( $event_date ) );
// Store current event's month in this format: March
$month = wp_date( 'F', strtotime( $event_date ) );
// Store the numeric month for sorting later in this format: 08
$month_num = wp_date( 'm', strtotime( $event_date ) );
// If the $year_month_post_ids array does not have the year of event being looped over as one of its keys, set it as the key with an empty array as the value
if ( ! isset( $year_month_post_ids[ $year ] ) ) {
$year_month_post_ids[ $year ] = [];
}
// If the $year_month_post_ids array's $year key does not have the month being looped over as one of its keys, set it as the key with an empty array as the value
if ( ! isset( $year_month_post_ids[ $year ][ $month ] ) ) {
$year_month_post_ids[ $year ][ $month ] = [];
}
// If the $year_months array does not have the year of event being looped over as one of its keys, set it as the key with an empty array as the value
if ( ! isset( $year_months[ $year ] ) ) {
$year_months[ $year ] = [];
}
// If the current event's month is not in the $year_months array, add it with the month number as the key
if ( ! in_array( $month, $year_months[ $year ] ) ) {
$year_months[ $year ][ $month_num ] = $month;
}
// Sort the months within each year in ascending order
foreach ( $year_months as $year => $months ) {
ksort( $months );
$year_months[ $year ] = $months;
}
$year_month_post_ids[ $year ][ $month ][] = $post->ID;
}
return [
'years' => array_keys( $year_month_post_ids ), // https://d.pr/i/Z1mUgn
'year_months' => $year_months, // https://d.pr/i/mnbM34
'data' => $year_month_post_ids, // https://d.pr/i/vzZBQZ
];
}
}
// Helper function to get looping parent query Id by level. Default is parent of the current loop.
// https://itchycode.com/bricks-builder-useful-functions-and-tips/
if ( ! function_exists( 'itchy_get_bricks_looping_parent_query_id_by_level' ) ) {
function itchy_get_bricks_looping_parent_query_id_by_level( $level = 1 ) {
global $bricks_loop_query;
if ( empty( $bricks_loop_query ) || $level < 1 ) {
return false;
}
$current_query_id = BricksQuery::is_any_looping();
if ( ! $current_query_id ) {
return false;
}
if ( ! isset( $bricks_loop_query[ $current_query_id ] ) ) {
return false;
}
$query_ids = array_reverse( array_keys( $bricks_loop_query ) );
if ( ! isset( $query_ids[ $level ] )) {
return false;
}
if ( $bricks_loop_query[ $query_ids[ $level ] ]->is_looping ) {
return $query_ids[ $level ];
}
return false;
}
}
// Helper function to get looping parent query object by level.
if ( ! function_exists( 'bl_get_loop_object_by_level' ) ) {
function bl_get_loop_object_by_level( $level = 1 ) {
return BricksQuery::get_loop_object( itchy_get_bricks_looping_parent_query_id_by_level( $level ) );
}
}
Replace all instances of event_date with your date field name/ID.
In
'post_type' => 'event',
ensure that the correct post type identifier is used.
If using Meta Box, set the Save format as Ymd and change
[
'key' => 'event_date',
'compare' => '>=',
'value' => date( 'Ymd' ),
],
to
[
'key' => 'event_date',
'compare' => '>=',
'value' => date( 'Ymd' ),
'type' => 'NUMERIC' // Change to NUMERIC since we're storing as Ymd format
],
Step 2
Whitelist the bl_get_loop_object_by_level function.
Ex.:
<?php
add_filter( 'bricks/code/echo_function_names', function() {
return [
'bl_get_loop_object_by_level'
];
} );
You should also add any other necessary functions (native or custom) in your Bricks instance in addition to bl_get_loop_object_by_level. This can be checked at Bricks → Settings → Custom code by clicking the Code review button.
More info on whitelisting can be found here.
Step 3
Edit the Page in which you would like to output the events with Bricks.
Copy this JSON (of a section) and paste.
Event Year loop:

Event Month Loop:

Post Loop:

$month = BricksQuery::get_loop_object( itchy_get_bricks_looping_parent_query_id_by_level() );
$year = BricksQuery::get_loop_object( itchy_get_bricks_looping_parent_query_id_by_level(2) );
return [
'post_type' => 'event',
'posts_per_page' => 100, // a large number
'no_found_rows' => true,
'post__in' => bl_get_event_year_data()['data'][$year][$month],
'orderby' => 'post__in',
];
Click Sign code button and Save.
The Heading element’s text is set to:
{echo:bl_get_loop_object_by_level(2)} {echo:bl_get_loop_object_by_level}
Post Title element text is set to:
{acf_event_date:d M Y}: {post_title:link}
That’s it! Check the front end.
Reference
https://www.advancedcustomfields.com/resources/date-picker/#query-posts-within-date-range