The new WordPress editor (Gutenberg) comes with lots of pre built blocks (Paragraph, List, Image, etc.). Each block has its own markup which wraps the content of it. This markup is generally generic enough that it works with most of the themes. But there are cases where you would like to change the appearance of these blocks in some way.
Let's say we would like to add a spacing control to the existing image block (core/image
) where we can choose between a small
, medium
or large
margin which should be added below the image.
1. Add spacing attribute
Before we can add a custom control to the image block we need a place where we can save the chosen value. In Gutenberg this is done with block attributes.
To add a custom attribute to an existing block we can use the blocks.registerBlockType
filter like this:
import assign from 'lodash.assign';
const { addFilter } = wp.hooks;
const { __ } = wp.i18n;
// Enable spacing control on the following blocks
const enableSpacingControlOnBlocks = [
'core/image',
];
// Available spacing control options
const spacingControlOptions = [
{
label: __( 'None' ),
value: '',
},
{
label: __( 'Small' ),
value: 'small',
},
{
label: __( 'Medium' ),
value: 'medium',
},
{
label: __( 'Large' ),
value: 'large',
},
];
/**
* Add spacing control attribute to block.
*
* @param {object} settings Current block settings.
* @param {string} name Name of block.
*
* @returns {object} Modified block settings.
*/
const addSpacingControlAttribute = ( settings, name ) => {
// Do nothing if it's another block than our defined ones.
if ( ! enableSpacingControlOnBlocks.includes( name ) ) {
return settings;
}
// Use Lodash's assign to gracefully handle if attributes are undefined
settings.attributes = assign( settings.attributes, {
spacing: {
type: 'string',
default: spacingControlOptions[ 0 ].value,
},
} );
return settings;
};
addFilter( 'blocks.registerBlockType', 'extend-block-example/attribute/spacing', addSpacingControlAttribute );
Let me explain this code step by step:
First we need to define some variables which are used at multiple places in our example.
// Enable spacing control on the following blocks
const enableSpacingControlOnBlocks = [
'core/image',
];
In the enableSpacingControlOnBlocks
array we can list all existing blocks where the spacing control should be available. In our example we just add the core/image
block.
// Available spacing control options
const spacingControlOptions = [
{
label: __( 'None' ),
value: '',
},
{
label: __( 'Small' ),
value: 'small',
},
{
label: __( 'Medium' ),
value: 'medium',
},
{
label: __( 'Large' ),
value: 'large',
},
];
The spacingControlOptions
array lists all available options which can be chosen in our spacing control element.
const addSpacingControlAttribute = ( settings, name ) => {
// Do nothing if it's another block than our defined ones.
if ( ! enableSpacingControlOnBlocks.includes( name ) ) {
return settings;
}
// Use Lodash's assign to gracefully handle if attributes are undefined
settings.attributes = assign( settings.attributes, {
spacing: {
type: 'string',
default: spacingControlOptions[ 0 ].value,
},
} );
return settings;
};
This is our callback function where the custom attribute gets registered. We first check if the block is one of our defined blocks in the enableSpacingControlOnBlocks
array. If that's the case we add our new attribute spacing
to the already existing blocks attributes.
addFilter( 'blocks.registerBlockType', 'extend-block-example/attribute/spacing', addSpacingControlAttribute );
Last but not least we need to add this function to the blocks.registerBlockType
filter.
The core/image
block now has a new attribute spacing
which we can use to save our chosen value.
2. Add spacing control
As a next step we need to create the spacing control element to the blocks InspectorControl
.
The result should look like this:
Gutenberg provides the editor.BlockEdit
hook to extend the blocks edit
component (which includes the InspectorControl
part).
const { createHigherOrderComponent } = wp.compose;
const { Fragment } = wp.element;
const { InspectorControls } = wp.editor;
const { PanelBody, SelectControl } = wp.components;
/**
* Create HOC to add spacing control to inspector controls of block.
*/
const withSpacingControl = createHigherOrderComponent( ( BlockEdit ) => {
return ( props ) => {
// Do nothing if it's another block than our defined ones.
if ( ! enableSpacingControlOnBlocks.includes( props.name ) ) {
return (
<BlockEdit { ...props } />
);
}
const { spacing } = props.attributes;
// add has-spacing-xy class to block
if ( spacing ) {
props.attributes.className = `has-spacing-${ spacing }`;
}
return (
<Fragment>
<BlockEdit { ...props } />
<InspectorControls>
<PanelBody
title={ __( 'My Spacing Control' ) }
initialOpen={ true }
>
<SelectControl
label={ __( 'Spacing' ) }
value={ spacing }
options={ spacingControlOptions }
onChange={ ( selectedSpacingOption ) => {
props.setAttributes( {
spacing: selectedSpacingOption,
} );
} }
/>
</PanelBody>
</InspectorControls>
</Fragment>
);
};
}, 'withSpacingControl' );
addFilter( 'editor.BlockEdit', 'extend-block-example/with-spacing-control', withSpacingControl );
We first create a Higher Order Component (HOC) withSpacingControl
. It receives the original block BlockEdit
component which we can extend.
if ( ! enableSpacingControlOnBlocks.includes( props.name ) ) {
return (
<BlockEdit { ...props } />
);
}
We again check if the block is one of our defined blocks in the enableSpacingControlOnBlocks
array. If this isn't the case we return the original BlockEdit
component.
const { spacing } = props.attributes;
// add has-spacing-xy class to block
if ( spacing ) {
props.attributes.className = `has-spacing-${ spacing }`;
}
return (
<Fragment>
<BlockEdit { ...props } />
<InspectorControls>
<PanelBody
title={ __( 'My Spacing Control' ) }
initialOpen={ true }
>
<SelectControl
label={ __( 'Spacing' ) }
value={ spacing }
options={ spacingControlOptions }
onChange={ ( selectedSpacingOption ) => {
props.setAttributes( {
spacing: selectedSpacingOption,
} );
} }
/>
</PanelBody>
</InspectorControls>
</Fragment>
);
Otherwise we wrap the original BlockEdit
component in a Fragment
and add a SelectControl
inside the InspectorControls
of the block where the spacing can be chosen. Additionally we add a specific has-spacing-xy
class to the original component to be able to style the block in the editor if needed.
addFilter( 'editor.BlockEdit', 'extend-block-example/with-spacing-control', withSpacingControl );
To make it work we add our withSpacingControl
component to the editor.BlockEdit
filter.
The spacing control should now be visible in the WordPress editor as soon as you add an image block.
3. Add margin to the saved markup
We can already select a spacing value in the backend but the change is not yet visible in the frontend. We need to use our spacing
attribute to render a specific margin below the image block. We can use the blocks.getSaveContent.extraProps
filter to do exactly that. With this filter we can modify the properties of the blocks save element. What we will do is map our chosen spacing to a pixel value which we add as margin-bottom
to the image block.
/**
* Add margin style attribute to save element of block.
*
* @param {object} saveElementProps Props of save element.
* @param {Object} blockType Block type information.
* @param {Object} attributes Attributes of block.
*
* @returns {object} Modified props of save element.
*/
const addSpacingExtraProps = ( saveElementProps, blockType, attributes ) => {
// Do nothing if it's another block than our defined ones.
if ( ! enableSpacingControlOnBlocks.includes( blockType.name ) ) {
return saveElementProps;
}
const margins = {
small: '5px',
medium: '15px',
large: '30px',
};
if ( attributes.spacing in margins ) {
// Use Lodash's assign to gracefully handle if attributes are undefined
assign( saveElementProps, { style: { 'margin-bottom': margins[ attributes.spacing ] } } );
}
return saveElementProps;
};
addFilter( 'blocks.getSaveContent.extraProps', 'extend-block-example/get-save-content/extra-props', addSpacingExtraProps );
In the addSpacingExtraProps
function we create a little mapping object where which we can use to map our selected spacing value (small
, medium
or large
) to an appropriate pixel value.
Afterwards we add this value as margin-bottom
to the style
attribute of the block.
That's already it! Our selected spacing value gets mapped to an appropriate margin-bottom value and is added to the saved markup of the block.
Wrap up
It's not that hard to extend an existing Gutenberg block. So before creating a new block think about if it would make more sense to extend an existing block to your own needs.
This tutorial is also available as a WordPress plugin on GitHub: https://github.com/liip/extend-block-example-wp-plugin. Feel free to use it as a starting point to extend Gutenberg blocks in your WordPress site.