When designing a complex WordPress layout, achieving custom sorting and grouping in the core/query
block can be challenging. In this post, we’ll show how to modify the core/query
block to group posts by category and sort them by a custom field, such as menu_order
, within each category. We’ll also add a custom control to enable this functionality directly from the block editor, so users can toggle grouping by category as needed.
Here’s how we tackled the challenge and enhanced the core/query
block’s capabilities.
The Problem: Grouping Posts by Category and Sorting by Custom Order
The default core/query
block in WordPress provides flexibility to display posts based on various parameters, such as date or title. However, it lacks a built-in feature to group posts by category and then apply a custom sorting order within each category.
To achieve this, we need:
- Grouping by Category: All posts should first be grouped by category.
- Sorting by Custom Field (
menu_order
): Within each category, posts should be ordered bymenu_order
rather than by date or other typical fields.
This can be useful for layouts where structured navigation is required, such as:
- Product catalogues
- Content directories
- Featured content lists
Solution Overview: Extending the Core Query Block
To solve this, we need to:
- Add a Custom Toggle for Grouping by Category: In the block editor, we’ll add a new toggle control,
Group by Category
, in the query settings of thecore/query
block. - Modify the Query Logic: When the
Group by Category
toggle is enabled, we’ll adjust the query parameters to sort by category and then bymenu_order
.
Step 1: Adding the Group by Category
Control to the core/query
Block
To give users the option to toggle this feature, we use a higher-order component (HOC) to inject the Group by Category
setting into the block’s inspector panel. This HOC adds the control, allowing users to turn grouping on or off from the editor:
const withCustomQueryControls = (BlockEdit) => {
return (props) => {
// Only apply to the Query block
if (props.name !== 'core/query') {
return <BlockEdit {...props} />;
}
const { attributes, setAttributes } = props;
return (
<>
<BlockEdit {...props} />
<InspectorControls>
<PanelBody title="Query Settings">
<ToggleControl
label="Group by Category"
checked={attributes.groupByCategory}
onChange={(value) => setAttributes({ groupByCategory: value })}
/>
</PanelBody>
</InspectorControls>
</>
);
};
};
// Apply the higher-order component to the block
addFilter('editor.BlockEdit', 'my-plugin/sort-by-category-group', withCustomQueryControls);
Explanation
- Conditional Application: This HOC only applies to the
core/query
block. If a different block is being edited, it skips applying the control. - Adding the Toggle Control: In the
InspectorControls
, we add aPanelBody
with aToggleControl
forGroup by Category
. This control is linked to an attribute,groupByCategory
, which we’ll define in the next step.
Step 2: Registering Custom Attributes
We add groupByCategory
and two gap-related attributes, rowGap
and columnGap
, to the core/query
block using a filter on blocks.registerBlockType
. These attributes will be stored with the block and accessible in the editor.
const addGapSizeAttributes = (settings, name) => {
if (name !== 'core/query') {
return settings;
}
// Add the gapSize and columnGap attributes to the block
settings.attributes = {
...settings.attributes,
groupByCategory: {
type: 'boolean',
default: false, // Default value for grouping by category
},
};
return settings;
};
addFilter('blocks.registerBlockType', 'my-custom-query-extension/add-gap-size-attributes', addGapSizeAttributes);
Explanation
- Attribute Definitions: The
groupByCategory
attribute is a boolean, initially set tofalse
. When enabled, it will trigger a grouped and sorted display.rowGap
andcolumnGap
attributes provide additional layout flexibility, allowing users to adjust spacing directly.
Step 3: Modifying the Query Logic
Next, we intercept the block rendering process with the pre_render_block
filter and modify the query using query_loop_block_query_vars
. This allows us to adjust the query to group by category and sort within each category by menu_order
.
add_filter('pre_render_block', 'myplugin_pre_render_block', 10, 2);
function myplugin_pre_render_block($pre_render, $parsed_block)
{
// Check if the core/query block has the groupByCategory attribute enabled
if (
isset($parsed_block['blockName'], $parsed_block['attrs']['groupByCategory']) &&
'core/query' === $parsed_block['blockName'] &&
true === $parsed_block['attrs']['groupByCategory']
) {
// Modify the query parameters for grouped and sorted display
add_filter(
'query_loop_block_query_vars',
function ($query, $block) use ($parsed_block) {
// Set query ordering to group by category and then by menu_order
$query['orderby'] = [
'category' => 'ASC', // Group by category
'menu_order' => 'ASC' // Sort within each category by menu_order
];
$query['order'] = 'ASC';
// Optional: Filter by star rating if specified in block attributes
if (isset($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;
}
Explanation of the Query Modifications
- Query Sorting Logic: The query’s
orderby
parameter specifies bothcategory
andmenu_order
. This ensures that posts are first grouped by category and then sorted within each category bymenu_order
. - Optional Filter: We include a conditional filter based on a
starRating
attribute. This allows users to filter posts by specific criteria when needed.
Benefits of This Approach
- User-Friendly Control: The custom
Group by Category
toggle allows users to apply complex grouping and sorting without coding. - Adaptable Layout: With additional attributes like
rowGap
andcolumnGap
, users can further customise the block layout for a polished and professional appearance. - Enhanced Query Flexibility: This solution leverages
pre_render_block
andquery_loop_block_query_vars
filters for precise control over WordPress queries, enabling advanced displays for any block that relies on dynamic content.