← All Articles A Product of Kinsa Creative

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:

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.