WordPress 6.1 introduced new methods for extending the Query Loop block. This is a significant milestone because it puts a lot of functionality in the hands of plugin developers at little cost, at least in terms of code. Instead of building custom blocks to query posts, extenders need to only filter the existing functionality in core WordPress.
The Query Loop block is a workhorse in building websites out of blocks. It is the foundation for displaying content from posts, pages, and other custom post types (CPTs).
Before WordPress 6.1, its primary use case was displaying a limited subset of what was previously possible when compared to its PHP counterpart: WP_Query
. This meant that developers, particularly when handling data for CPTs, were often building entire custom blocks on their own.
Today, extenders can build atop a solid foundation for outputting nearly any type of content with minimal code on their part. The changes in version 6.1 allow plugin authors to skip the block development aspect and extend the built-in Query Loop block.
A few examples of the ways this can be used include:
- Displaying a grid of products by a price meta field.
- Business directory listing businesses by location.
- Leaderboard for a peer-to-peer fundraiser.
- Outputting book reviews by rating.
For this walk-through, you will learn how to tackle the last item in that list: listing book review posts. You will build a WordPress plugin from start to finish. The result will be a Query Loop block variation that looks similar to the following screenshot:
The basic methods in this simple tutorial can also be applied to more complex projects.
Tutorial Steps
Requirements
Aside from some baseline JavaScript development knowledge and familiarity with block development, you should have these tools available on your machine:
- Node/NPM Development Tools
- WordPress Development Site
- Code Editor
For further information on setting these up, visit the Development Environment guide in the Block Editor Handbook.
Setting up content
For this tutorial, assume that you have a client who likes to write book reviews from time to time and wants to show off the latest reviews in various places on their site, such as on a custom page. The client has a custom category titled “Book Reviews” and a few posts already written.
Recreate this scenario in your development environment.
First, add a new “Book Reviews” category and make note of the category ID. You will need this later. Then, create at least three example posts that are assigned to this category and give each a featured image.
Plugin setup
Create a new plugin in your wp-content/plugins
directory. Name it something like book-reviews-grid
(the exact name is not particularly important). Now, add the following files with this specific structure:
book-reviews-grid
/index.php
/package.json
/src
/index.js
You can change index.php
to whatever you want. It is your plugin’s primary PHP file.
PHP setup
In your plugin’s primary PHP file, add the plugin header with some basic information:
<?php
/**
* Plugin Name: Book Reviews Grid
* Version: 1.0.0
* Requires at least: 6.1
* Requires PHP: 7.4
*/
// Additional code goes here…
This will be the only PHP file you will need for this tutorial, and all PHP code will go into it.
Build process setup
First, open your package.json
file and add the start
script. This will be used for the build process. You can add other fields, such as name
and description
if you want.
{
"scripts": {
"start": "wp-scripts start"
}
}
This tutorial requires the @wordpress/scripts
package, which can be installed via the command line:
npm install @wordpress/scripts --save-dev
Once you have everything set up, type the following in your command line program:
npm run start
Aside from the required start
command, you can find all of the available scripts via the @wordpress/scripts package and add them to your package.json
if needed.
Building a simple Query Loop variation
The process for registering simple Query Loop variation (one without any custom query variable integration) requires only a few dozen lines of code. You must import registerBlockVariation
and use it to register the variation.
JavaScript: Building the variation
At the top of your src/index.js
file, add the following line of code:
import { registerBlockVariation } from '@wordpress/blocks';
Now, you need two pieces of information. First, decide on a unique name for the variation. book-reviews
will work for now. The second bit of data needed is the ID of the “Book Reviews” category that you created earlier in this walk-through.
Take both of those values and assign them to constants, as shown in the following snippet:
const VARIATION_NAME = 'book-reviews';
const REVIEW_CATEGORY_ID = 8; // Assign custom category ID.
Now, it is time to register the variation. First, add a few basic properties, such as the name, title, and more, as shown in the following code block:
registerBlockVariation( 'core/query', {
name: VARIATION_NAME,
title: 'Book Reviews',
icon: 'book',
description: 'Displays a list of book reviews.',
isActive: [ 'namespace' ],
// Other variation options...
} );
There are two necessary options to set when registering the variation for the core/query
block:
- The
name
property should match your unique variation name. - The
isActive
property should be an array with thenamespace
attribute (you will define this attribute in the next step).
From this point, the variation is mostly customizable, but let’s walk through this one step at a time, adding new options for the variation. The next piece to build is the variation’s attributes. Attributes can match any that the Query Loop block accepts.
The one extra required attribute is namespace
. It must match the variation name so that WordPress will be able to check whether it is the active variation.
For this tutorial, the variation displays the latest six posts within the “Book Reviews” category. It also has a wide layout in a three-column grid. Feel free to customize the options to your liking.
registerBlockVariation( 'core/query', {
// ...Previous variation options.
attributes: {
namespace: VARIATION_NAME,
query: {
postType: 'post',
perPage: 6,
offset: 0,
taxQuery: {
category: [ REVIEW_CATEGORY_ID ]
}
},
align: 'wide',
displayLayout: {
type: 'flex',
columns: 3
}
},
// Other variation options...
} );
Developers can also choose which of the default WordPress controls are available by setting the allowedControls
array (by default, all controls are shown). These appear as block options in the interface. For a full list of controls and their definitions, visit the allowed controls section in the Block Editor Handbook.
The following example, adds the order
and author
controls:
registerBlockVariation( 'core/query', {
// ...Previous variation options.
allowedControls: [
'order',
'author'
],
// Other variation options...
} );
Finally, you should add some inner blocks for the variation. The first top-level block should always be core/post-template
. The following code snippet uses the core Post Featured Image and Post Title blocks with no customizations, but feel free to add other blocks and set default options for each block.
registerBlockVariation( 'core/query', {
// ...Previous variation options.
innerBlocks: [
[
'core/post-template',
{},
[
[ 'core/post-featured-image' ],
[ 'core/post-title' ]
],
]
]
} );
For a full overview of the available options, visit the following resources:
PHP: Loading the JavaScript
With the base JavaScript handled, now you must load the JavaScript file itself. The build process will generate two files:
- build/index.js: The JavaScript file to be loaded.
- build/index.asset.php: An array of dependencies and a version number for the script.
The following code snippet should be added to index.php
. It gets the generated asset file if it exists and loads the script in the editor:
add_action( 'enqueue_block_editor_assets', 'myplugin_assets' );
function myplugin_assets() {
// Get plugin directory and URL paths.
$path = untrailingslashit( __DIR__ );
$url = untrailingslashit( plugins_url( '', __FILE__ ) );
// Get auto-generated asset file.
$asset_file = "{$path}/build/index.asset.php";
// If the asset file exists, get its data and load the script.
if ( file_exists( $asset_file ) ) {
$asset = include $asset_file;
wp_enqueue_script(
'book-reviews-variation',
"{$url}/build/index.js",
$asset['dependencies'],
$asset['version'],
true
);
}
}
With this code in place, you should be able to add the “Book Reviews” variation via the block inserter or a slash command (e.g. /book reviews
) in the block editor:
You could stop at this point of the tutorial if your variation doesn’t require any customizations to the queried posts beyond what the core Query Loop block handles out of the box.
Integrating post metadata into the variation
Now, let’s dive into a slightly more advanced scenario that builds upon the existing code. You will build a control for users to display book reviews based on a post meta key and value pair.
The crucial aspects for this to work are the filter hooks that WordPress provides. Once you learn how to use these, you can expand them to custom post types and other real-world projects.
Setting up post metadata
You will need a post meta key of rating
with a meta value between 1
and 5
that is attached to one or more of the posts in the Book Reviews category. The easiest way to do this is to use the Custom Fields panel on the post editing screen.
Note: If you do not see the Custom Fields panel, you can enable it from Options (⋮ icon) > Preferences > Panels menu in the editor.
Head back to each of your book review posts and add in a rating value, as shown in the following screenshot:
In a real-world project, you will likely want to build proper form fields for the end-user to easily select a star rating. However, that is outside the scope of this tutorial.
JavaScript: Adding block variation controls
To add extra controls to your custom Query Loop variation, you will need to import a few additional modules into your script. Add the following code to the top of your src/index.js
file. You will use these as you build out the remainder of the functionality.
// ...Previous imports.
import { addFilter } from '@wordpress/hooks';
import { InspectorControls } from '@wordpress/block-editor';
import { PanelBody, SelectControl } from '@wordpress/components';
Next, build a quick helper function for determining if a block, based on its props, matches your variation. You will use this later in the code. Most of the work has already been done because you previously set up a variation name constant and namespace to check against.
const isBookReviewsVariation = ( props ) => {
const {
attributes: { namespace }
} = props;
return namespace && namespace === VARIATION_NAME;
};
Now, it is time to build a component for displaying a custom block panel section. You can add multiple fields here later. For now, it will house a form field for selecting a star rating.
The following code uses the SelectControl
component to create a dropdown select of each of the available ratings. This could just as easily be a radio list, button group, or an entirely custom React component. It is up to you.
The vital piece of this code is saving the star rating value to props.attributes.query.starRating
. You will need this later to modify the posts query.
const BookReviewControls = ( { props: {
attributes,
setAttributes
} } ) => {
const { query } = attributes;
return (
<PanelBody title="Book Review">
<SelectControl
label="Rating"
value={ query.starRating }
options={ [
{ value: '', label: '' },
{ value: 1, label: "1 Star" },
{ value: 2, label: "2 Stars" },
{ value: 3, label: "3 Stars" },
{ value: 4, label: "4 Stars" },
{ value: 5, label: "5 Stars" }
] }
onChange={ ( value ) => {
setAttributes( {
query: {
...query,
starRating: value
}
} );
} }
/>
</PanelBody>
);
};
Once you’ve built out the custom panel section and control, you must filter the Query Loop (core/query
) block to add in custom controls. That is where the earlier isBookReviewsVariation
helper function comes in. You will pass it the block’s props to determine if it is your custom variation. If it matches, add your controls.
export const withBookReviewControls = ( BlockEdit ) => ( props ) => {
return isBookReviewsVariation( props ) ? (
<>
<BlockEdit {...props} />
<InspectorControls>
<BookReviewControls props={props} />
</InspectorControls>
</>
) : (
<BlockEdit {...props} />
);
};
addFilter( 'editor.BlockEdit', 'core/query', withBookReviewControls );
At this point, you should have a visible section titled “Book Review” with a “Rating” select dropdown inside of it when your variation is in use, as shown in the following screenshot:
Selecting a rating should not change the queried posts at this point. There are still a couple of filters left to add to make it work.
PHP: Filtering the queried posts
You must add two PHP filters to make this work for both the editor and front end. Then, you will have a fully-working Query Loop variation with post meta integration.
The first filter will go on the rest_{$post_type}_query
hook. Because you are building this with the “post” post type, that hook name becomes rest_post_query
.
This filter will run on every query for that type, so you need to check for your custom query parameter (starRating
) before making any changes. You can check for this via the callback function’s $request
parameter, which provides an instance of the WP_REST_Request
class. Use its get_param()
method to check for the custom query parameter.
If the starRating
value is set, you only need to pass the meta key and value back as query arguments. To do this, add the following code to your plugin’s primary PHP file:
add_filter( 'rest_post_query', 'myplugin_rest_book_reviews', 10, 2 );
function myplugin_rest_book_reviews( $args, $request ) {
$rating = $request->get_param( 'starRating' );
if ( $rating ) {
$args['meta_key'] = 'rating';
$args['meta_value'] = absint( $rating );
}
return $args;
}
Now, test your previously-built rating control in the editor. As shown in the following screenshot, only the posts with the selected rating are queried:
While this works in the editor, you also need to use the pre_render_block
hook to run some custom code when the block is rendered on the front end. Then, you will need to nest a second filter inside of that callback on the query_loop_block_query_vars
hook using an anonymous function. The reason for this is that you need access to the parsed block attributes.
If it sounds a little convoluted, it is. Ideally, there will be a slightly less complex method for doing this in the future.
add_filter( 'pre_render_block', 'myplugin_pre_render_block', 10, 2 );
function myplugin_pre_render_block( $pre_render, $parsed_block ) {
// Determine if this is the custom block variation.
if ( 'book-reviews' === $parsed_block['attrs']['namespace'] ) {
add_filter(
'query_loop_block_query_vars',
function( $query, $block ) use ( $parsed_block ) {
// Add rating meta key/value pair if queried.
if ( $parsed_block['attrs']['query']['starRating'] ) {
$query['meta_key'] = 'rating';
$query['meta_value'] = absint( $parsed_block['attrs']['query']['starRating'] );
}
return $query;
},
10,
2
);
}
return $pre_render;
}
Now, you should have the same queried posts on the front end of the site as shown in the editor.
With this foundation in place, you can extend this to other projects. Essentially, you can build any type of query that you need with a few filters and custom controls. While this tutorial may seem a bit long, it contains fewer than 200 lines of code in total, and that is a major win in comparison to building out fully-fledged blocks.
What types of Query Loop block variations do you have in mind?
Props to @bph, @mburridge, and @webcommsat for technical and editorial feedback.
Leave a Reply