WordPress 5.8 introduced the Query Loop block — a native block that renders a list of posts using a configurable query without any PHP. It replaced the legacy Posts block and became the foundation of Full Site Editing’s dynamic content capabilities. Developers can extend it in two ways: by registering block variations (pre-configured Query Loop setups with a name and icon), and by filtering the underlying WP_Query arguments it generates using the query_loop_block_query_vars filter. This means custom post types, custom meta ordering, and taxonomies can all be surfaced through the Query Loop block without writing a custom block from scratch — making it the right starting point for any “list of posts” block feature in a theme or plugin targeting WordPress 5.8+.
Problem: A theme needs a "Latest Events" section on the home page: a Query Loop block that automatically queries the custom post type event, ordered by a custom meta field _event_date, and appears in the block inserter as its own variation named "Events List" with a calendar icon.
Solution: Register a Query Loop block variation in JavaScript and filter query_loop_block_query_vars in PHP to inject the CPT and meta ordering when the variation's namespace is detected.
// variations.js — register the "Events List" Query Loop variation
import { registerBlockVariation } from '@wordpress/blocks';
registerBlockVariation( 'core/query', {
name: 'my-theme/events-list',
title: 'Events List',
description: 'Display upcoming events ordered by date',
icon: 'calendar-alt',
isDefault: false,
// These attributes pre-configure the Query Loop block
attributes: {
namespace: 'my-theme/events-list', // ← key: lets PHP identify this variation
query: {
perPage: 6,
postType: 'event',
orderBy: 'date',
order: 'desc',
inherit: false,
},
},
// Which inner blocks to start with
innerBlocks: [
[ 'core/post-template', {}, [
[ 'core/post-title' ],
[ 'core/post-excerpt' ],
] ],
[ 'core/query-pagination' ],
],
// This variation is the active one if namespace matches
isActive: ( attributes ) => attributes.namespace === 'my-theme/events-list',
} );
<?php
// PHP: intercept the query when this variation renders
add_filter( 'query_loop_block_query_vars', function ( array $query, WP_Block $block, int $page ): array {
// Only modify queries from our specific variation
if ( ( $block->context['namespace'] ?? '' ) !== 'my-theme/events-list' ) {
return $query;
}
// Force correct post type and ordering by meta
$query['post_type'] = 'event';
$query['meta_key'] = '_event_date';
$query['orderby'] = 'meta_value';
$query['order'] = 'ASC';
// Optionally: only future events
$query['meta_query'] = [
[
'key' => '_event_date',
'value' => gmdate( 'Y-m-d' ),
'compare' => '>=',
'type' => 'DATE',
],
];
return $query;
}, 10, 3 );
// Enqueue the variation JS
add_action( 'enqueue_block_editor_assets', function () {
wp_enqueue_script(
'my-theme-query-variations',
get_template_directory_uri() . '/js/variations.js',
[ 'wp-blocks', 'wp-element' ],
wp_get_theme()->get( 'Version' ),
true
);
} );
NOTE: The namespace attribute in the variation's attributes object is the bridge between JavaScript and PHP — it is stored in the block's serialised HTML and passed to PHP as $block->context['namespace']. Without it, the PHP filter cannot distinguish your variation's queries from all other Query Loop blocks on the page. The query_loop_block_query_vars filter was added in WordPress 6.0; for 5.8–5.9, use the pre_render_block filter instead and check $parsed_block['attrs']['namespace'] manually.