WP 5.8 Query Loop Block: Register a Block Variation and Filter query_loop_block_query_vars

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.