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_orderrather 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/queryblock. - Modify the Query Logic: When the
Group by Categorytoggle 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/queryblock. If a different block is being edited, it skips applying the control. - Adding the Toggle Control: In the
InspectorControls, we add aPanelBodywith aToggleControlforGroup 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
groupByCategoryattribute is a boolean, initially set tofalse. When enabled, it will trigger a grouped and sorted display.rowGapandcolumnGapattributes 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
orderbyparameter specifies bothcategoryandmenu_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
starRatingattribute. This allows users to filter posts by specific criteria when needed.
Benefits of This Approach
- User-Friendly Control: The custom
Group by Categorytoggle allows users to apply complex grouping and sorting without coding. - Adaptable Layout: With additional attributes like
rowGapandcolumnGap, users can further customise the block layout for a polished and professional appearance. - Enhanced Query Flexibility: This solution leverages
pre_render_blockandquery_loop_block_query_varsfilters for precise control over WordPress queries, enabling advanced displays for any block that relies on dynamic content.