When a plugin registers many custom blocks, having them scattered under the generic “Widgets” or “Common” categories in the block inserter makes them hard for content editors to find. WordPress allows registering custom block categories via a PHP filter, and grouping multiple blocks under a single brand category is the recommended pattern for any plugin that registers three or more blocks. The block_categories_all filter (WP 5.8+, replacing the older block_categories filter) allows adding, removing, or reordering categories. Each category needs a slug and a title, and optionally an icon (a dashicon slug or a React SVG element). Block collections — registered via registerBlockCollection() in JavaScript — add a branded section to the inserter’s search results and can optionally include a custom icon that appears next to the category name in the block inserter.
Problem: A plugin registers ten custom blocks (hero, testimonial, pricing table, FAQ accordion, team member, counter, icon box, CTA, timeline, and portfolio grid). They need to appear under a "Acme Blocks" category in the inserter, sorted before all WordPress core categories, with a custom icon.
Solution: Add the category via block_categories_all in PHP, and optionally register a block collection via registerBlockCollection() in JavaScript for the enhanced inserter UI.
<?php
// ── Add custom block category (PHP, WP 5.8+) ──────────────────────────
// Use block_categories_all instead of the deprecated block_categories
add_filter( 'block_categories_all', function ( array $categories ): array {
// Prepend our category to put it FIRST in the inserter
return array_merge(
[
[
'slug' => 'acme-blocks',
'title' => __( 'Acme Blocks', 'textdomain' ),
'icon' => 'layout', // dashicon slug (optional, shown in WP 6.0+)
],
],
$categories
);
}, 10 );
// ── Register blocks under the custom category ─────────────────────────
// In each block's block.json, set:
// "category": "acme-blocks"
// Or when calling register_block_type():
register_block_type( __DIR__ . '/blocks/hero', [
'category' => 'acme-blocks',
] );
register_block_type( __DIR__ . '/blocks/testimonial', [
'category' => 'acme-blocks',
] );
// ── Optionally: enqueue the block collection script ───────────────────
add_action( 'enqueue_block_editor_assets', function () {
wp_enqueue_script(
'acme-block-collection',
plugin_dir_url( __FILE__ ) . 'js/block-collection.js',
[ 'wp-blocks', 'wp-element' ],
'1.0.0',
true
);
} );
// block-collection.js
// registerBlockCollection adds a branded section to the inserter's
// "Browse All" panel (the detailed inserter), grouped by your namespace
const { registerBlockCollection } = wp.blocks;
registerBlockCollection( 'acme', {
title: 'Acme Blocks',
// icon can be a dashicon string, a WordPress icon component, or custom SVG:
icon: {
src: (
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"/>
</svg>
),
},
} );
// All blocks whose name starts with 'acme/' are grouped under this collection
// in the "Browse All" inserter view. No need to enumerate them individually.
NOTE: The registerBlockCollection() namespace must match the prefix of your block names — if your blocks are named acme/hero, acme/testimonial, the collection namespace is 'acme'. The block collection affects the "Browse All" inserter panel only; the quick inserter (the inline inserter when you press Enter or click the block appender) uses the category field from block.json. Both mechanisms can be used simultaneously for the best discovery experience. The block_categories_all filter receives the full list of registered categories as the first argument — you can also remove core categories by filtering the array, though removing categories that core blocks use will cause those blocks to appear uncategorised.