Getting started with WordPress block development

Photo by Mike Hindle on Unsplash

WordPress has had its block editor Gutenberg for a while now and most people use it for creating pages and blog posts. But sometimes the core blocks are not enough and you find yourself in need of more advanced features. In this post I will give a basic introduction to developing blocks using React (jsx) and WordPress’ build tool @wordpress/scripts. I recommend that you are familiar with React and javascript in general before reading this post and that you have some knowledge on running npm in the terminal. If you want to code along make sure you have a recent node.js version installed on your machine.

TL;DR If you want to jump right in, I have created a plugin template that is ready to go. Download from github: https://github.com/cmk2179/wp-block-starter or use:

npx degit https://github.com/cmk2179/wp-block-starter <path/to/wordpress/wp-content/plugins/my-block-plugin>

Take a look at the README to get started.

Setting up WordPress

I will assume that you have a webserver like apache or nginx set up already or that you know how to set it up. Download the latest version of WordPress and unpack it to the webroot folder.

Open a terminal and navigate to the wp-content/plugins folder. Create the base plugin by issuing the following command: npx degit https://github.com/cmk2179/wp-block-starter wp-block-starter.

Go the plugin section in the WordPress administration and activate the plugin. Now we are ready to start developing.

If you want to follow along, then delete the existing sample-block in src/blocks as the goal of this post is to create it from scratch.

Creating a block

Lets create the first block. We want to create it in the folder src/blocks, by switching directory and running the scaffolding command:

cd src/blocks
npx @wordpress/create-block --no-plugin --no-wp-scripts --title "Sample block" --namespace wp-block-starter sample-block

This will take care of the heavy lifting, creating the files that are necessary for a block. By default this script will create a plugin and install a lot of packages for us, but in this case we just want to scaffold a block without all the bells and whistles. By passing the --no-plugin and --no-wp-scripts only the files needed for the block is created. The output folder will be given the name specified as the last argument to the scaffold script and the folder will be created in the current working directory. The reason we need to put the block into the src folder is that the @wordpress/scripts package automatically scans this folder for block.json files.

The first file we will take a look at is the block.json that was generated. This is were all the metadata about the block is registered like name, description, icon, attributes, etc. The generated block.json does not include all possible options, please refer to the documentation for block.json for all possible options.

Lets build the block and setup file watching so we can test the changes whenever the browser is reloaded. To build and watch, run this command: npm run dev.

This will create a build folder with the output from the compilation of blocks in the src folder. Next step is to register the block in WordPress, to do this we use the register_block_type function. Using the given setup we just need to specify the path to the folder containing the block.json file like this:

// wp_block_starter.php
<?php
/**
 * Plugin name: WP Block Starter
 */

if (!function_exists("wp_block_starter_register_blocks")) {
    function wp_block_starter_register_blocks()
    {
        register_block_type(__DIR__ . "/build/blocks/sample-block"):
    }
}

add_action("init", "wp_block_starter_register_blocks");

The block is now registered and we can start using it in the block editor. Lets create a new post and use the block by searching for “Sample block”. The default content generated by the scaffolding script should be shown.

Default content from block in editor – generated by the scaffold script
Default content from block in frontend – generated by the scaffold script

Block development

From here on out the development can begin. Lets make a simple block with the following features:

It should be possible to…

  • …add other blocks to the block (inner blocks)
  • …change the background color of the block
  • …change the text color of the block

Note that blocks are very interactive in the editor, but when saving the post the blocks are saved as static html. This post will focus on these static blocks and in the future I might add another post on dynamic blocks as well. To support this functionality each block has two methods, an edit and a save method. The edit method is used to create the component during editing in the block editor and the save method is used to generate the markup that is saved to the database when saving the post. If you are looking for info on dynamic blocks there is a tutorial on the WordPress documentation site.

Adding blocks as children

For most usecases this is fairly simple, as WordPress provides a core block for this. Open up the edit.js file and lets add the innerblocks component – lets also change the paragraph tag to a div:

// Update the import as well to make use of the InnerBlocks component
import { InnerBlocks, useBlockProps } from "@wordpress/block-editor";

export default function Edit() {
  return (
    <div {...useBlockProps()}>
        {__("Sample block – hello from the editor!", "sample-block")}
        <InnerBlocks template={[["core/paragraph"]]}></InnerBlocks> // <-- add the InnerBlocks component here
    </div>
  );
}

You might have noticed that we added the template property to the InnerBlocks component, this is because by default the innerblock will have no height, making it difficult to see that it is there at all. By adding the paragraph WordPress will add a placeholder text that makes it easy to see that inner blocks can be added.

We also need to update the save function, to ensure it will properly save the block. Open up the save.js file and lets add the innerblocks component here as well.

// Update the import as well to make use of the InnerBlocks component
import { InnerBlocks, useBlockProps } from "@wordpress/block-editor";

export default function save() {
  return (
    <div {...useBlockProps.save()}>
      {"Sample block – hello from the saved content!"}
      <InnerBlocks.Content></InnerBlocks.Content> // <-- add the InnerBlocks.Content component here
    </div>
  );
}

In the save method we used the InnerBlocks.Content component, which is a special component that handles rendering the inserted inner blocks as html for saving the content to the database.

Sometimes you will come across this error, due to the way wordpress serializes and deserializes blocks.

When a post or page is loaded in the editor, WordPress compares the loaded content with the newly generated content from the block and if they differ in certain ways then WordPress will show this error. Mostly the “Attempt Block Recovery” action will suffice, but sometimes it is necessary to add some inner blocks again. Always remember to save the post after resolving this conflict.

Changing the background color

Before starting this task, lets first clean up the styling generated by the scaffold script, that is causing the background to be blue. Open style.scss and delete the styling for the block:

.wp-block-wp-block-starter-sample-block {
  /* delete these three lines */
  background-color: #21759b;
  color: #fff;
  padding: 2px;
}

Next we need to learn about attributes, since this is how properties are persisted across saves and how they are passed between the edit and the save method. Attributes are part of the block.json metadata and the documentation is found here.

We will need to specify an attribute to hold the color for the background. Open up the block.json file and add the following lines:

"attributes": {
  "backgroundColor": {
    "type": "string",
    "default": "transparent"
  }
},

We will add a color picker in the inspector, to do this we use the InspectorControls component which should be a sibling of the content rendered in the editor, the edit function should look like this after implementing it:

export default function Edit({ attributes, setAttributes }) {
  return (
    <>
      <InspectorControls>
        <PanelBody title="Colors">
          <BaseControl label="Background color">
            <ColorPalette
              value={attributes.backgroundColor}
              onChange={(backgroundColor) => setAttributes({ backgroundColor })}
            ></ColorPalette>
          </BaseControl>
        </PanelBody>
      </InspectorControls>

      <div
        {...useBlockProps()}
        style={{ backgroundColor: attributes.backgroundColor }}
      >
        {__("Sample block – hello from the editor!", "sample-block")}
        <InnerBlocks template={[["core/paragraph"]]}></InnerBlocks>
      </div>
    </>
  );
}

There are a lot of different ways to implement the layout in the inspector, for now I will use the simplest, which is a PanelBody. This component makes an expandable panel section that makes it easy to group related settings. For a more advanced layout, consider the ToolsPanel component – the documentation and an example can be found here.

We should also update the save function to make it work in the published post:

export default function save({ attributes }) {
  return (
    <div
      {...useBlockProps.save()}
      style={{ backgroundColor: attributes.backgroundColor }}
    >
      {"Sample block – hello from the saved content!"}
      <InnerBlocks.Content></InnerBlocks.Content>
    </div>
  );
}

Changing the text color

This should be pretty straightforward. Please try this on your own and if you need help, expand the section below to show the solution.

Solution

edit.js

export default function Edit({ attributes, setAttributes }) {
  return (
    <>
      <InspectorControls>
        <PanelBody title="Colors">
          <BaseControl label="Background color">
            <ColorPalette
              value={attributes.backgroundColor}
              onChange={(backgroundColor) => setAttributes({ backgroundColor })}
            ></ColorPalette>
          </BaseControl>
          <BaseControl label="Text color">
            <ColorPalette
              value={attributes.color}
              onChange={(color) => setAttributes({ color })}
            ></ColorPalette>
          </BaseControl>
        </PanelBody>
      </InspectorControls>

      <div
        {...useBlockProps()}
        style={{
          backgroundColor: attributes.backgroundColor,
          color: attributes.color,
        }}
      >
        {__("Sample block – hello from the editor!", "sample-block")}
        <InnerBlocks template={[["core/paragraph"]]}></InnerBlocks>
      </div>
    </>
  );
}

save.js

export default function save({ attributes }) {
  return (
    <div
      {...useBlockProps.save()}
      style={{
        backgroundColor: attributes.backgroundColor,
        color: attributes.color,
      }}
    >
      {"Sample block – hello from the saved content!"}
      <InnerBlocks.Content></InnerBlocks.Content>
    </div>
  );
}

block.json

"attributes": {
  "backgroundColor": {
    "type": "string",
    "default": "transparent"
  },
  "color": {
    "type": "string",
    "default": "black"
  }
},

Conclusion

This was a brief introduction on how to get up and running with block development for the WordPress Gutenberg block editor. I am still learning and improving my knowledge on WordPress and block development and this post summarizes some of the things that I think are the most basic things needed to get started with block development. I had a hard time when I started learning block development because I could not find any good posts on how to get started properly, so I had to do some trial and error and dig through a lot of documentation on WordPress blocks. I hope that linking the documentation that I have used and providing a short introduction to how a plugin with multiple blocks could be set up, will help others who wants to develop their own Gutenberg blocks.