Creating Custom Gutenberg Blocks for WordPress
Create a plugin to house the code for the custom Gutenberg blocks
To create a new plugin, create a directory for it: in the wp-content/plugins
directory, create a new directory for the plugin e.g. my-custom-blocks
. As a convention, this name should be snake case and will be referenced throughout as a namespace for the blocks.
Within that directory, make a new file by the same name e.g. my-custom-blocks.php
.
Add to that a minimum of the following:
<?php
/*
Plugin Name: My Custom Blocks
Description: This plugin adds my custom blocks.
Author: First Last
*/
Create custom Gutenberg blocks in the theme instead of a plugin
Instead of creating a plugin for the custom Gutenberg blocks, they can be part of the theme. Create two nested directoies within the theme: src/blocks
:
cd my-theme
mkdir -p src/blocks
Create the block using the scaffolding script according to the directions that follow. A perk to this method is that there are fewer advantages to grouping your blocks as within a plugin (like shared code between blocks since there is intuitive access to the theme's static and functional files), so there is less need to restructure the blocks after building them with the scaffolding.
Register the custom block. In the functions.php
file add:
function register_my_custom_block()
{
register_block_type( dirname(__FILE__) . '/src/blocks/my-custom-block/build/my-custom-block/block.json' );
}
add_action('init', 'register_my_custom_block');
Create a custom Gutenberg block
Dynamic Blocks
Dynamic blocks are useful if you want to add a block that outputs the contents of ACF fields or some arbitrary PHP code. Dynamic blocks require editing a render.php
file for output on the frontend and editing the edit.js
file to define any output within the WP Admin.
The output of the render.php
file can be rendered in the edit.js
file using the <ServerSideRender>
component to allow a DRY method of previewing what will be published while editing. In edit.js
:
import ServerSideRender from '@wordpress/server-side-render';
...
export default function Edit({ attributes, setAttributes }) {
<ServerSideRender block="my-custom-blocks/my-custom-block" attributes={attributes} />
}
Standard Blocks
To create a standard block, in the following code example, avoid the --variant="dynamic"
flag.
Running the Scaffolding Script
Navigate to the plugin directory and use the WP Scaffold to create the custom block. In a shell, where my-custom-block
is the name of the block and you want it to be of the dynamic type:
cd wp-content/plugins/my-custom-blocks/
npx @wordpress/create-block@latest --variant="dynamic" my-custom-block
Modifying the Structure to Allow for Multiple Custom Blocks within a Single Plugin
The scaffolding script creates a directory at the same level as my-custom-blocks/src
.
Change directory into wp-content/plugins/my-custom-blocks/my-custom-block
and delete everything but the src
directory within it.
cd my-custom-block
find . -maxdepth 1 -not -name 'src' -not -name '.' -exec rm -rf {} +
Move the files from the src
directory to the top level of the directory so they are immediate descendents of wp-content/plugins/my-custom-blocks/my-custom-block/
:
mv src/my-custom-block/* ./
Delete wp-content/plugins/my-custom-blocks/my-custom-block/src
:
rm -rf src
Move the new directory into a src/
directory at the root of the plugin so the path to the custom block is now something like wp-content/plugins/my-custom-blocks/src/my-custom-block
:
cd ..
mkdir src
mv my-custom-block src/
Register the block in wp-content/plugins/my-custom-blocks/my-custom-blocks.php
. Edit that file to include:
function my_custom_blocks_init(): void {
register_block_type(__DIR__ . '/build/my-custom-block');
}
add_action('init', 'my_custom_blocks_init');
Add a package.json
file. This replaces the package.json
file we deleted from my-custom-block
after creating it with the scaffolding. As a direct descendant of my-custom-blocks/
, create the package.json
file. Add to it:
{
"name": "my-custom-blocks",
"version": "0.1.0",
"description": "My Custom Blocks",
"author": "First Last",
"license": "GPL-2.0-or-later",
"main": "build/index.js",
"scripts": {
"build": "wp-scripts build --webpack-src-dir=src --output-path=build",
"start": "wp-scripts start --webpack-src-dir=src --output-path=build",
"format": "wp-scripts format",
"lint:css": "wp-scripts lint-style",
"lint:js": "wp-scripts lint-js",
"packages-update": "wp-scripts packages-update",
"plugin-zip": "wp-scripts plugin-zip",
"test:unit": "wp-scripts test-unit-js"
},
"devDependencies": {
"@wordpress/scripts": "^29.0.0"
}
}
Edit the src/my-custom-block/block.json
file:
- Replace
create-block
withmy-custom-blocks
in thename
. - Specify a new icon. The icon can be chosen from the WordPress Dashicons Resource page.
- Revise the
description
to accurately describe the purpose of the block.
Nesting Blocks Inside of Other Blocks
To create a block that allows children, a common pattern for setting up a specific grid or flex layout or card structure, in the edit.js
file of the parent block, in the returned component, add, where we are allowing only the my-custom-blocks/my-custom-block
card to be added and we have included a first card in the template:
<InnerBlocks
template={[["my-custom-blocks/my-custom-block"]]}
allowedBlocks={["my-custom-blocks/my-custom-block"]}
/>
Modify the block.json
file as well to indicate that the block supports inner blocks and which blocks to allow:
{
...
"supports": {
"innerBlocks": true
},
"allowedBlocks": [
"core/paragraph",
"core/spacer",
"my-custom-blocks/my-custom-block"
],
...
}
To allow any child blocks, the edit.js
file simply needs to return the InnerBlocks
component:
import { InnerBlocks, useBlockProps } from "@wordpress/block-editor";
import "./editor.scss";
export default function Edit() {
return (
<InnerBlocks />
);
}
Modify the block.json
file as well to indicate that the block supports inner blocks and omit defining allowedBlocks
which will allow all blocks by default:
{
...
"supports": {
"innerBlocks": true
},
...
}
The save.js
file in either case returns the content of the inner blocks:
import { InnerBlocks, useBlockProps } from "@wordpress/block-editor";
export default function save() {
return (
<InnerBlocks.Content />
);
}
Restricting a Block to Be a Child of a Certain Other Type of Block
To limit a block so it can only be used in a parent block, in the block.json
file of the child block, add a key for parent
and set the value to an array of parent blocks (in this example my-custom-blocks/my-custom-block-wrapper
), e.g.:
"parent": ["my-custom-blocks/my-custom-block-wrapper"],
Building
The wp-content/plugins/my-custom-blocks/package.json
file lists the scripts available. During development, start
will compile the blocks on demand while build
will do a one-time build. To run the scripts, from the wp-content/plugins/my-custom-blocks/
directory, run:
npm run start
or
npm run build
Adding a Combination of Blocks to the Plugin as a Pattern
Often when theming a website with Gutenberg custom blocks, a combination of blocks will be used to create an effect. Collecting the group as a pattern provides a useful way for content creators to find and add them.
To do this, assemble the combination of Gutenberg blocks on a new post. Select the whole combination of blocks and right-click to create a new pattern. You probably don't want it to be global unless the blocks are all dynamic. With global patterns, a change to one use affects all the others. Save the combination and then navigate to the theme → patterns and export the JSON.
Open the JSON in a text editor, copy out the value of content
and append it to wp-content/plugins/my-custom-blocks/my-custom-blocks.php
, wrapping it in a function and hooking that function like so:
function my_custom_blocks_patterns_init(): void
{
register_block_pattern('my-custom-blocks/my-pattern', array(
'title' => __('My Pattern', 'my-custom-blocks'),
'categories' => array('featured'), // adding the pattern to the featured collection makes it easier for content editors to find
'source' => 'plugin',
'content' => ""
));
}
add_action('init', 'my_custom_blocks_patterns_init');
Feedback?
Email us at enquiries@kinsa.cc.