Creating a New Block for the Gutenberg Editor

Hello, and welcome to the site! In this first post, we’ll be exploring how to use the @wordpress/create-block package to create a plugin containing a single block for the Gutenberg editor.

As you’ll see it’s an extremely useful tool for quickly scaffolding out new blocks, and includes some really cool features out-of-the-box such as automatic script enqueueing.

I decided to write about this first so I can refer back to it in subsequent articles. This is much better than keep repeating the same steps over and over, which would be pretty redundant. Plus, having it in one place will make it easier to update in the future.

Setting Up Your Local WP Environment

Before we dive into creating a block, just a quick word about your local setup.

You’ll need to have WordPress running locally on your computer in order to view and access blocks in the Gutenberg editor. Node.js (12.0.0 or above) and npm (6.9.0 or above) are also required to be able to use @wordpress/create-block from the command-line. Node.js can be installed (for all operating systems) here, which also includes npm as part of the installation process.

If you don’t already have WordPress installed locally I’d recommend using something like LocalWP which is 100% free, includes plenty of features, and is a very popular choice among WordPress developers at the moment.

Creating a New Block

Navigate to the folder on your computer where you want to create the new block plugin. Usually, this will be in the ./wp-content/plugins/ folder of your local WordPress site.

Open a new command-line interface window and enter the following:

npx @wordpress/create-block myblock

The slug you specify (myblock in our case) will be used for the plugin name, text domain, and internal block name.

Depending on the speed of your system, and available memory, this will create a new WordPress plugin containing a single block, in around a couple of minutes.

That’s all there is to it! Congratulations, you’re now a block development pro.

Plugin Structure

I usually edit block source code with VS Code but use whatever editor you prefer. It doesn’t really make any difference which one you end up using. If you’re new to development some other editors you might want to consider include:

Let’s take a look now at the files created for us by @wordpress/create-block.

Pay particular attention to the files in the ./src folder as well as ./block.json. These files contain the block source code which are edited to customize the features of your block.

The ./build folder contains the compiled block scripts and styles. These are automatically enqueued in the block editor (and on the frontend) to make your block available on any page it’s been added to.

This is a huge benefit and is worth mentioning again in case you missed it. Normally, you’d have to manually enqueue your scripts/styles, add block dependencies (and unique version identifier), and then add extra code to check if the block is used in the main content area of the current page and conditionally enqueue block assets.

The paths to compiled block files are defined in ./block.json. The rest of the enqueuing process is handled for you automatically!

{
	...
	"editorScript": "file:./build/index.js",
	"editorStyle": "file:./build/index.css",
	"style": "file:./build/style-index.css"
}

Block dependencies, and unique file version identifier (used for caching purposes) are located in ./build/index.asset.php which is auto-generated every time your block source code compiles.

<?php return array('dependencies' => array('wp-block-editor', 'wp-blocks', 'wp-element', 'wp-i18n', 'wp-polyfill'), 'version' => '43c40d0321c8d03aff4431f9a73cee8d');

This means that you never again have to worry about manually adding correct script dependencies (and in the right order!), or unique version identifier when enqueueing files. You also don’t need to concern yourself about your block being added to all pages globally.

All of this is now handled for you behind the scenes which is really convenient, leaving you to focus solely on block development. For me, this ease of block creation and workflow is a significant step forward.

New Kid on the Block

We mentioned ./block.json briefly in the last section which may look new to you. This innocent-looking file is key to modern block development and is now the officially recommended way to define block types.

To clarify, this doesn’t replace the usual block registration functions, however. You’ll still need to register your block via PHP and JavaScript but the difference now is that only block.json should contain definitions for block properties (name, title, attributes etc.).

{
	"apiVersion": 2,
	"name": "create-block/myblock",
	"version": "0.1.0",
	"title": "Myblock",
	"category": "widgets",
	"icon": "smiley",
	"description": "Example block written with ESNext standard and JSX support – build step required.",
	"supports": {
		"html": false
	},
	"textdomain": "myblock",
	"editorScript": "file:./build/index.js",
	"editorStyle": "file:./build/index.css",
	"style": "file:./build/style-index.css"
}

The default ./block.json generated by @wordpress/create-block contains all the information necessary to define our block type, but it doesn’t include all available properties. See the official documentation for the complete reference.

This approach of defining block properties as metadata in a JSON file is beneficial as it allows code sharing between PHP and JavaScript, and provides added performance benefits such as lazy loading of block assets (if the current theme supports it).

Top Tip! Even though the @wordpress/create-block package creates a single block, you’ll undoubtedly want to create more blocks in the same plugin at some point. Just remember, that a block.json file is required for each block you create.

Exploring the Block Code

The main entry point for your block is ./src/index.js so let’s start there: (comments removed for clarity)

import { registerBlockType } from '@wordpress/blocks';
import './style.scss';
import Edit from './edit';
import save from './save';

registerBlockType( 'create-block/myblock', {
	edit: Edit,
	save,
} );

This is where the block is registered via JavaScript and so we first need to pull in registerBlockType() from the @wordpress/blocks package. Other imports include block styles and two components for rendering the block in the editor (Edit), and on the frontend (save).

Splitting the main block index.js file out into separate components like this is a good idea to keep things well organized as the Edit and save components can easily grow quite large!

Note: style.scss is imported here rather than in the save component as it’s required for the editor and frontend.

Edit Component

The block editor code for the Edit component is located in ./src/edit.js: (comments removed for clarity)

import { __ } from '@wordpress/i18n';
import { useBlockProps } from '@wordpress/block-editor';
import './editor.scss';

export default function Edit() {
	return (
		<p { ...useBlockProps() }>
			{ __( 'Myblock – hello from the editor!', 'myblock' ) }
		</p>
	);
}

This is about as simple as you can get for a block definition. After importing the block editor styles and the __() and useBlockProps() functions, a simple React function component is exported. When rendered, this component returns a single HTML paragraph element.

You can still use class-based React components, of course, the choice is entirely down to your own preference. There’s still plenty of examples of class components in the core Gutenberg code, but the pattern seems to be that newer additions to the codebase are usually implemented via function components.

Top Tip! I’d recommend sticking with function components where possible to make it easier to follow along with newer code examples.

If you’re familiar with function components then you’ve probably encountered React hooks at some point. These allow for advanced functionality similar to class components, such as managing component state.

useBlockProps() is an example of a custom WordPress React hook, which adds the necessary attributes and event handlers required for blocks to work correctly. This hook is actually a requirement if your block is using the new API version 2. Any blocks created using the @wordpress/create-block package will automatically use this API version and is defined in ./block.json.

Note: WordPress includes many custom React hooks to make block development easier when working with function components.

Save Component

The block editor code for the save component is located in ./src/save.js: (comments removed for clarity)

import { __ } from '@wordpress/i18n';
import { useBlockProps } from '@wordpress/block-editor';

export default function save() {
	return (
		<p { ...useBlockProps.save() }>
			{ __( 'Myblock – hello from the saved content!', 'myblock' ) }
		</p>
	);
}

This is a very simple component too and is almost identical to Edit but watch out for the hook usage. This time we’re using useBlockProps.save() which outputs attributes and event handlers specific to the save component. You’ll get an error if you use just useBlockProps().

Where’s the Code?

As you can see from the Edit and save components, @wordpress/create-block doesn’t insert much in the way of starter code. It’s very much just bare bones. This makes it’s very easy to get started customizing your block as there’s very little boilerplate code to remove/refactor.

However, if you’re new to block development this can be a slight negative too as blocks created via @wordpress/create-block don’t contain any attributes, or settings sidebar controls, by default, so you’ll need to add these manually. We’ll cover how to do this later on.

How Do I Look?

Even though it can take a few minutes for @wordpress/create-block to create a block from scratch, it installs all the necessary npm packages, and compiles the build files, for you so you can install and activate the plugin right away. I think this is a nice touch, making the block instantly accessible.

Here’s how the default block looks inside the editor once it has been added to a page.

The frontend view looks very similar too but has a different text message to demonstrate different React components are used to render the block in the editor and on the frontend.

You get a bonus point if you spotted that the editor block also has a thin red border around the text. This isn’t displayed in the frontend block as the border CSS is located in editor.scss which is only imported for the Edit component.

Tweaking the Code

There are a couple of minor optimizations you can make to the default block which are worth mentioning. First, in ./src/index.js we can rename the two instances of Edit to edit and use the same shorthand notation as save.

Note: You can also update Edit in ./src/edit.js too if you like but it’s not mandatory as the Edit component is a default export and can be renamed to whatever you like when importing it.

The block name used in registerBlockType has already been defined in ./block.json so we can just import that rather than specify the block name twice. (Props to Ryan Welcher for this tip!)

edit.js now looks like this:

import { registerBlockType } from '@wordpress/blocks';
import './style.scss';
import edit from './edit';
import save from './save';
import json from '../block.json';

const { name } = json;

registerBlockType( name, {
	edit,
	save,
} );

These changes mean that both PHP and JavaScript register block functions are now completely decoupled from your block-specific code. You can effectively forget about them as they’ll now be exactly the same in every block you create.

// PHP
register_block_type( __DIR__ );
// JavaScript
registerBlockType( name, { edit, save } );

These are only minor changes but it makes your block code even more streamlined which can only be a good thing!

First Configuration Steps

Now that you know how to create a block, and install/activate it inside WordPress, the next step is to actually begin customizing it by adding new features. This requires active watching of the block source code files so SCSS and JavaScript can be automatically recompiled whenever new changes are saved.

To start the watch process enter the following commands (assuming you’re still in the same directory npx @wordpress/create-block myblock was executed):

cd myblock
npm start

Let’s customize our block by adding two text fields and a color picker to the editor. These will allow you to change the text displayed in the editor and on the frontend, and also update the block background color.

Note: To store changes to blocks we need to create some variables. These are known as block attributes in Gutenberg.

Top Tip! If you’re familiar with React then you can think of block attributes as being similar to React state. Instead of setState we use setAttributes to update block settings. You can still use React state inside blocks, but to persist changes you’ll need to use attributes as these are automatically saved to the WordPress database every time the page is saved.

I won’t be covering the specifics of block customizations in too much detail right now as this is meant to be more of a demo of what you can do. So don’t worry if not everything seems clear at this stage, I’ll be covering many topics related to block development in minute detail in future articles.

Block Attributes

Let’s add our block attributes first. As you might have guessed these should be defined in ./block.json along with our other block properties.

Add a new attribute property to ./block.json:

"attributes": {
  "editorText": {
    "type": "string",
    "default": "Myblock – hello from the editor!"
  },
  "frontendText": {
    "type": "string",
    "default": "Myblock – hello from the saved content!"
  },
  "backgroundColor": {
    "type": "string",
    "default": "#21759b"
  }
}

Each attribute has a unique name identifier and a type. You can also specify a default value but this is optional.

Settings Sidebar Controls

We’ll need to add our block controls somewhere in the block editor. Fortunately, each block has its own settings sidebar which can be used for this very purpose and is very easy to use. First, we need to import a few new components in edit.js:

import {
	InspectorControls,
	useBlockProps
} from '@wordpress/block-editor';
import {
	PanelBody,
	PanelRow,
	TextControl,
	ColorPicker
} from '@wordpress/components';

To be able to access block attributes inside the edit and save components we need to pass them block props. This isn’t done by default but is easy enough. Just add props to each of the function components. And, as our block attributes are stored as an object inside props we can extract them using destructuring:

// edit.js
export default function edit(props) {
	const { attributes: { editorText, frontendText, backgroundColor }, setAttributes } = props;
	...
}
// save.js
export default function save(props) {
	const { attributes: { frontendText, backgroundColor } } = props;
	...
}

Notice how we’re also extracting the setAttributes function from props in the block editor? We’ll use this to update our attributes.

The new components we imported into our edit component above can now be added to the editor to enable us to update block attribute values. Any components added inside <InspectorControls> will be automatically be added to the block sidebar settings panel.

Update the return statement inside edit.js to add the sidebar settings panel as well as the text, and color picker controls.

return (
  <>
    <p {...useBlockProps()} style={{ backgroundColor: backgroundColor }}>
      {editorText}
    </p>
    <InspectorControls>
      <PanelBody>
        <PanelRow>
          <TextControl
            label="Editor Text"
            autoComplete="off"
            value={editorText}
            onChange={(newText) => setAttributes({ editorText: newText })}
          />
        </PanelRow>
        <PanelRow>
          <TextControl
            label="Frontend Text"
            autoComplete="off"
            value={frontendText}
            onChange={(newText) => setAttributes({ frontendText: newText })}
          />
        </PanelRow>
        <PanelRow>
          <ColorPicker
            color={backgroundColor}
            onChangeComplete={(value) => setAttributes({ backgroundColor: value.hex })}
            disableAlpha
          />
        </PanelRow>
      </PanelBody>
    </InspectorControls>
  </>
);

Notice how we’re also wrapping everything with the shorthand Fragment. Otherwise, we’d have two top-level components which is not allowed in React.

The code should be reasonably self-explanatory. In the main block content, we’re still outputting the paragraph element as before, but instead of displaying fixed text, we’re using the editorText attribute value instead.

We’re also using the backgroundColor attribute value to set the block background color via a style HTML attribute. This means we no longer need to set a fixed background color in style.scss so this can be commented out or removed.

Whenever the sidebar settings controls are updated, the corresponding editor text and background color are now immediately updated too in real-time.

Note: Nothing will appear to change right away in the editor if you change the frontend text control value as this is displayed on the frontend of the site only. But you can see changes at any time by clicking the ‘Preview’ button while in the editor.

The return statement for our save component is pretty similar to edit except we don’t need to add any sidebar settings controls.

return (
  <p { ...useBlockProps.save() } style={{ backgroundColor: backgroundColor }}>
    {frontendText}
  </p>
);

And when previewed, the frontend looks like this:

While making changes to the save component you’ll no doubt come across the dreaded block validation warning sooner or later.

This is very common during development and is nothing to worry about it. Whenever you make a change to the save component and see this warning message, simply click on the blue ‘Attempt Block Recovery’ button and all should be well.

Note: If your block is released publicly then changes made to the save component need to be managed properly so that end users don’t see the block validation warning when accessing the new version of the block. This is called deprecation and is outside the scope of this tutorial but you can read more about it here.

Taking it Further

There’s so much more you can do, we’ve barely begun to scratch the surface of what’s possible. But that’s really the point of this site, to explore as many aspects of block/editor development as possible.

In the meantime if you want to extend the block plugin further, for practice, here are a few suggestions you can try on your own to get a feel for customizing blocks:

  1. Create a new <SidebarSettings /> component to contain the sidebar settings code.
  2. Add this component to a separate sidebar-settings.js file and import into edit.js.
  3. Add new color pickers for the block text color too, and editor border color.
  4. Add a <RangeControl /> to set the border width in the block editor.

I’ve added the myblock block plugin to GitHub for convenience. You can access it here if you’d like an easier starting point before attempting the customization suggestions above.

InnerBlock Thoughts

By now you should have the confidence to spin-up, and customize, new WordPress blocks whenever you need to.

Not only is @wordpress/create-block extremely useful for block development in general, it also allows you to quickly spin up test blocks for experimentation and prototyping; which can then be discarded or developed further as required. This can be a bit of a tedious process otherwise, and I’m loving the freedom developers now have to just create blocks at will.

This is just the first of many articles on block development. I started this project as there seems to be a significant demand for up-to-date block-related content, so hopefully, this will become a popular resource.

Please let me know what you think, I’d love to hear your thoughts. Come and say hello on Twitter, and make sure to follow me for more snippets of wisdom! 🙂

Let's Learn Block Development Together!

If you want to learn all about block editor development then please signup to the newsletter below. I've got some awesome content planned which I can't wait to share with you!