You’re looking to tweak those core Gutenberg blocks, giving them extra functionality without reinventing the wheel. The good news is, WordPress provides a couple of powerful tools for exactly this: addFilter and BlockEdit. This article will walk you through how to use them, focusing on practical examples so you can start customizing your blocks today.
Before we dive into the “how,” let’s quickly touch on the “why.” You might want to:
Enhance Functionality
Add new settings, attributes, or controls that aren’t available by default. Think about custom styling options, specific data attributes, or integration with other plugins.
Improve User Experience
Streamline the editing process by adding helpful UI elements or pre-filling certain values based on context.
Enforce Brand Guidelines
Ensure consistency across your site by adding custom classes or attributes that adhere to your design system.
Avoid Custom Block Bloat
Sometimes, creating a whole new block for a minor tweak is overkill. Extending an existing one is more efficient and maintains WordPress’s core functionality.
If you’re interested in enhancing your WordPress site with custom functionalities, you might find it beneficial to explore how to extend core Gutenberg blocks using addFilter and BlockEdit. This approach allows developers to modify existing blocks and add new features seamlessly. For further insights on enhancing your web development skills, you can check out a related article on sending emails using CyberPanel, which provides valuable information on managing your server and optimizing email communications. You can read more about it here.
Understanding addFilter
addFilter is your primary tool for hooking into various parts of the Gutenberg editor. It allows you to modify data, components, or settings that WordPress provides. Think of it as an interception point where you can inspect and change things before they’re fully processed.
The addFilter Signature
The basic structure of addFilter looks like this:
“`javascript
wp.hooks.addFilter(
‘namespace/filterName’, // The filter hook to tap into
‘namespace/nameOfYourFunction’, // A unique name for your callback
(originalValue, …additionalArguments) => {
// Your logic to modify originalValue
return modifiedValue;
}
);
“`
Let’s break down those parameters:
namespace/filterName
This is crucial. It identifies which filter you’re trying to use. WordPress provides many filters, each designed for a specific purpose. For extending blocks, we’ll primarily focus on filters related to block registration and attribute modification. For example, blocks.registerBlockType allows you to modify block settings during registration.
namespace/nameOfYourFunction
This is a unique string that identifies your specific callback function. It’s good practice to use your plugin or theme’s namespace to avoid conflicts with other code.
The Callback Function
This is where your magic happens. It receives the original value (or values) that the filter is passing through, and you return the modified version. The arguments it receives depend entirely on the specific filter being used.
Common Filters for Block Extension
While there are many filters, a few are particularly relevant when extending core blocks:
blocks.registerBlockType
This filter is invoked when a block type is being registered. It allows you to modify the block’s settings object, which includes attributes, supports, editor components, and more. This is your go-to for adding new attributes or altering existing supports.
blocks.getBlockDefaultClassName
Useful if you want to apply a custom default CSS class to a block based on certain conditions.
editor.BlockEdit
This is a powerful one, and we’ll dedicate a whole section to it. It allows you to wrap or modify the BlockEdit component itself, giving you control over the block’s editor interface.
blocks.getSaveContent.extraProps
If you need to add extra props (like custom data attributes or additional classes) to the block’s HTML saved to the database without modifying the block’s attributes, this filter is your friend. It’s less common for direct extensions but good to know.
Introducing BlockEdit Overrides
While addFilter can modify static aspects of a block (like its attributes or supports), for truly altering the block’s editing experience, you’ll often need to interact with the BlockEdit component.
What is BlockEdit?
Every block, whether core or custom, has an edit function that defines its appearance and behavior in the editor. When you see a block in the Gutenberg canvas, you’re essentially looking at the output of its edit function. BlockEdit is the React component that renders this edit function.
The editor.BlockEdit Filter
This filter allows you to wrap the original BlockEdit component with your own component. This is often referred to as a Higher-Order Component (HOC) pattern.
“`javascript
import { createHigherOrderComponent } from ‘@wordpress/compose’;
import { addFilter } from ‘@wordpress/hooks’;
const withMyCustomEdit = createHigherOrderComponent( ( BlockEdit ) => {
return ( props ) => {
// Your custom logic here
// You can conditionally render things, add controls, etc.
return (
<>
{ / Maybe add a custom toolbar button or inspector control / }
{ / Example: render a custom control based on props.isSelected / }
{ props.isSelected && (
This is a custom message when the block is selected!
) }
>
);
};
}, ‘withMyCustomEdit’ );
addFilter(
‘editor.BlockEdit’,
‘my-plugin/with-my-custom-edit’,
withMyCustomEdit
);
“`
Let’s dissect this:
createHigherOrderComponent
This utility from @wordpress/compose helps in creating HOCs specifically for Gutenberg components. It takes two arguments:
- A function that receives the original component (
BlockEditin this case) and returns a new component that wraps it. - A unique name for your HOC, useful for debugging.
The Wrapper Component
Inside the createHigherOrderComponent callback, you define a new React functional component. This component receives all the props that the original BlockEdit would have received (like attributes, setAttributes, isSelected, clientId, etc.).
Crucially, you render to ensure the original block’s editor interface is still displayed. You can then add, remove, or modify elements around or within the original BlockEdit‘s rendering.
Accessing Block Properties
Within your wrapper component created by editor.BlockEdit, you have access to a wealth of information about the block through the props object:
props.attributes: The current attributes of the block.props.setAttributes: A function to update the block’s attributes. This is how you persist changes.props.isSelected: A boolean indicating if the block is currently selected in the editor.props.clientId: The unique ID of the block instance.props.name: The full name of the block (e.g.,'core/paragraph').props.className: The CSS classes applied to the block wrapper in the editor.
Step-by-Step Example: Adding a Custom Class to the Paragraph Block
Let’s combine addFilter and BlockEdit to add a toggle to the Paragraph block that applies a custom CSS class.
1. Registering the Custom Attribute
First, we need a way to store whether our custom class should be applied. We’ll add a new attribute to the Paragraph block using the blocks.registerBlockType filter.
“`javascript
// src/index.js (or wherever your main JavaScript file lives)
import { addFilter } from ‘@wordpress/hooks’;
addFilter(
‘blocks.registerBlockType’,
‘my-plugin/add-custom-paragraph-attribute’,
( settings, name ) => {
// Only apply to the core Paragraph block.
if ( name === ‘core/paragraph’ ) {
return {
…settings,
attributes: {
…settings.attributes, // Keep existing attributes
hasCustomBorder: {
type: ‘boolean’,
default: false,
},
},
};
}
return settings;
}
);
“`
Explanation:
- We use
blocks.registerBlockTypebecause we’re modifying the block’ssettingsobject, specifically itsattributes. - We check
name === 'core/paragraph'to ensure our changes only affect the Paragraph block. - We spread
...settingsand...settings.attributesto avoid overwriting existing settings and attributes. - We define
hasCustomBorderas a boolean attribute with a default value offalse.
2. Adding Controls to the Editor
Now that the attribute exists, we need a UI control in the editor to toggle it. This is where editor.BlockEdit comes in. We’ll add a ToggleControl to the block’s Settings Sidebar (Inspector Controls).
“`javascript
// src/index.js (continuing from the previous code)
import { createHigherOrderComponent } from ‘@wordpress/compose’;
import { InspectorControls } from ‘@wordpress/block-editor’;
import { ToggleControl, PanelBody } from ‘@wordpress/components’;
import { Fragment } from ‘@wordpress/element’;
const withCustomParagraphControl = createHigherOrderComponent( ( BlockEdit ) => {
return ( props ) => {
// Ensure we only affect the Paragraph block
if ( props.name !== ‘core/paragraph’ ) {
return
}
const { attributes, setAttributes, isSelected } = props;
const { hasCustomBorder } = attributes;
return (
{ isSelected && ( // Only show controls when block is selected
label=”Enable Custom Border” checked={ hasCustomBorder } onChange={ ( value ) => setAttributes( { hasCustomBorder: value } ) } help=”Adds a custom border style to this paragraph.” /> ) } ); }; }, ‘withCustomParagraphControl’ ); addFilter( ‘editor.BlockEdit’, ‘my-plugin/with-custom-paragraph-control’, withCustomParagraphControl ); “` Finally, we need to ensure that when the post is saved, the custom class is applied to the HTML output of the Paragraph block if Let’s stick with modifying “`javascript // src/index.js (continuing from the previous code) import { addFilter } from ‘@wordpress/hooks’; import { cloneElement } from ‘@wordpress/element’; /** * */ function addCustomParagraphSaveClass( element, block, attributes ) { // Only apply to the core Paragraph block. if ( block.name === ‘core/paragraph’ && attributes.hasCustomBorder ) { // Clone the element and add the class. // We ensure a className exists before appending. const existingClassName = element.props.className || ”; const newClassName = return cloneElement( element, { className: newClassName } ); } return element; } addFilter( ‘blocks.getSaveElement’, ‘my-plugin/add-custom-paragraph-save-class’, addCustomParagraphSaveClass ); “` To make this code work, you’ll typically need to: “`php
/** */ function my_gutenberg_extensions_enqueue_assets() { // Enqueue the compiled editor script. wp_enqueue_script( ‘my-gutenberg-extensions-editor’, plugins_url( ‘build/index.js’, __FILE__ ), // Adjust path if needed array( ‘wp-blocks’, ‘wp-dom-ready’, ‘wp-edit-post’, ‘wp-element’, ‘wp-data’, ‘wp-url’, ‘wp-api’, ‘wp-editor’, ‘wp-compose’, ‘wp-hooks’, ‘wp-components’ ), filemtime( plugin_dir_path( __FILE__ ) . ‘build/index.js’ ), // Version for cache busting true // Enqueue in the footer ); } add_action( ‘enqueue_block_editor_assets’, ‘my_gutenberg_extensions_enqueue_assets’ ); // Enqueue frontend styles for your custom border. function my_gutenberg_extensions_enqueue_styles() { wp_enqueue_style( ‘my-gutenberg-extensions-styles’, plugins_url( ‘build/style.css’, __FILE__ ), // Assuming you’ll have a build process for styles too array(), filemtime( plugin_dir_path( __FILE__ ) . ‘build/style.css’ ) ); } add_action( ‘wp_enqueue_scripts’, ‘my_gutenberg_extensions_enqueue_styles’ ); add_action( ‘admin_enqueue_scripts’, ‘my_gutenberg_extensions_enqueue_styles’ ); // For editor preview “` (All the JS code from steps 1, 2, and 3 goes here.) “`css / Example style for the custom border / .has-custom-paragraph-border { border: 2px solid #0073aa; padding: 10px; } “` After building your JavaScript (e.g., If you’re looking to enhance your understanding of extending core Gutenberg blocks, you might find it helpful to explore a related article that delves into the intricacies of using addFilter and BlockEdit. This resource provides valuable insights and practical examples that can complement your learning experience. For more information, you can check out this helpful article that covers various techniques and best practices in Gutenberg development. When extending blocks, especially core ones, keep these things in mind: If you change the While fine for a few modifications, adding many complex HOCs can impact editor performance. Be mindful of what you render and when. Your extensions should ideally not have side effects on other blocks or the editor itself, unless that’s the intended purpose (e.g., global settings). For more complex extensions, you might need to manage state beyond simple Beyond Inspector Controls, you might want to add buttons to the block toolbar for quick actions. This also leverages “`javascript import { BlockControls } from ‘@wordpress/block-editor’; import { ToolbarGroup, ToolbarButton } from ‘@wordpress/components’; import { Icon } from ‘@wordpress/element’; // For icons const withMyToolbarButton = createHigherOrderComponent( ( BlockEdit ) => { return ( props ) => { if ( props.name !== ‘core/paragraph’ ) { return } const { attributes, setAttributes } = props; const { hasCustomBorder } = attributes; return ( icon={ hasCustomBorder ? ‘yes’ : ‘no’ } label=”Toggle Custom Border” onClick={ () => setAttributes( { hasCustomBorder: ! hasCustomBorder } ) } isPressed={ hasCustomBorder } /> ); }; }, ‘withMyToolbarButton’ ); addFilter( ‘editor.BlockEdit’, ‘my-plugin/with-my-toolbar-button’, withMyToolbarButton ); “` What if you want to modify settings for all core blocks, or a specific set of them? The For example, to disable a specific support for all blocks: “`javascript addFilter( ‘blocks.registerBlockType’, ‘my-plugin/disable-custom-colors-globally’, ( settings, name ) => { // You could filter by a specific category, or check if it’s a core block, etc. // For simplicity, this example removes color support from ALL blocks. return { …settings, supports: { …settings.supports, color: false, // Disable color palette & background color }, }; } ); “` This is powerful but needs careful consideration, as it affects the entire editor experience. Extending core Gutenberg blocks with Explanation:
createHigherOrderComponent, InspectorControls, ToggleControl, PanelBody, and Fragment.withCustomParagraphControl wraps the BlockEdit component.props.name to target only the Paragraph block.attributes, setAttributes, and isSelected from props. to maintain standard functionality.isSelected && to conditionally display our custom controls only when the block is selected.PanelBody provides a collapsible section in the sidebar.ToggleControl is used for our boolean hasCustomBorder attribute. Its checked prop is bound to hasCustomBorder, and onChange updates the attribute using setAttributes.3. Applying the Class to the Saved Output
hasCustomBorder is true. We’ll use the blocks.getBlockDefaultClassName filter for this, although for applying extra classes based on attributes, blocks.getSaveContent.extraProps can also be used. A more direct way is to filter the block.save content inside an addFilter on blocks.getSaveElement.save output using a filter on blocks.getSaveElement for clear attribute-based class application.
${ existingClassName } has-custom-paragraph-border.trim();Explanation:
cloneElement from @wordpress/element because React elements are immutable; we can’t directly modify element.props.className.blocks.getSaveElement filter receives the element (the React element produced by the block’s save function), block (its registration settings), and attributes.core/paragraph block and if attributes.hasCustomBorder is true.cloneElement and pass a new className prop, appending ' has-custom-paragraph-border' to any existing classes.Putting It All Together (and Enqueuing)
my-gutenberg-extensions).index.php file for your plugin.src folder with an index.js file for your JavaScript.@wordpress/scripts) to compile your src/index.js into a build/index.js (or similar).my-gutenberg-extensions/index.php:
my-gutenberg-extensions/src/index.js:my-gutenberg-extensions/build/style.css:npm start if using @wordpress/scripts), activate the plugin, and you should see the “Enable Custom Border” toggle for Paragraph blocks and the class applied on save.Considerations for
BlockEdit and saveBackward Compatibility
save output of a block, existing content might break. WordPress handles this with block “deprecations,” but when extending core blocks this can be tricky. Using blocks.getSaveElement and applying additional classes or attributes is generally safer than completely restructuring the save output, as it often gracefully degrades. Modifying core block output should be done with extreme caution. Often, filtering blocks.getSaveContent.extraProps is safer for adding attributes.Performance
Side Effects
State Management
setAttributes. Consider using useState or useSelect/useDispatch from @wordpress/data if your extensions need to interact with global editor state or remote data.Advanced Scenarios
Adding Toolbar Buttons
editor.BlockEdit and components from @wordpress/block-editor like BlockControls and ToolbarGroup/ToolbarButton.Explanation:
BlockControls renders content in the block toolbar.ToolbarGroup groups buttons together.ToolbarButton is a clickable button. We link its onClick to setAttributes and adjust its appearance based on hasCustomBorder.Filtering Block Settings Globally
blocks.registerBlockType filter is still your entry point, but your conditional logic inside the callback becomes more general.Conclusion
addFilter and BlockEdit is a powerful way to tailor the editor to your specific needs without writing entire custom blocks from scratch. By understanding how to hook into the block registration process to add attributes and how to wrap the BlockEdit component to add custom controls, you can significantly enhance the functionality and user experience of your WordPress site. Remember to be mindful of compatibility and performance, and always test your changes thoroughly. Happy block extending!