Two ways to build Gutenberg Blocks

Last night I had the pleasure of presenting at the Denver WordPress Developer meetup about building Gutenberg Blocks. In this post, I want to present two (of many) approaches to building Gutenberg blocks.

TLDR;

For those that want to skip straight to the code, I’ve created 2 Github repos where we build the same block twice with different technical approaches. The README of each repo outlines the steps to build the blocks, and includes links to the DIFFS for each step, so you can follow along and build the blocks yourself. 

First, some context

Gutenberg is the new block-based editor coming to WordPress 5.0, currently scheduled for release on November 27, 2018.

This new editor allows users to compose content using “Blocks”. Blocks can be anything, from paragraphs of text, to images or galleries, to embedded videos, contact forms, dynamic content from post types or external sources. . .the possibilities are endless. 

Blocks have some conceptual similarities to shortcodes, in that blocks allow you inject rich elements into the content created with WordPress, but unlike shortcodes, Blocks provide a rich UI to interact with and configure. This rich UI is a JavaScript application, built on top of React and Redux, that is enqueued into the WordPress admin.

Two approaches to building blocks

Getting started building blocks with Gutenberg can be a struggle. At the moment, official documentation for Gutenberg is still very incomplete and even much of the existing documentation is outdated and incorrect. So it can be tough to figure out how to build blocks and what options there are to make the process of building blocks easier. 

Two (of many out there) projects that make it easier to get up and running with custom Gutenberg Blocks are Create Guten Block by Ahmad Awais, and Advanced Custom Fields.

In order to compare experiences, we’ll look at what it takes to make the same block using both Advanced Custom Fields and Create Guten Block. 

Build a testimonial block

The block we’re going to create is a simple “Testimonial” block, that allows for a testimonial text to be added, an avatar image, a citation name, some color options for setting the background color and text color, and some alignment options. 

Below is an GIF showing the final block in action. While the preview is of the block built using ACF, the goal is to build a comparable block using Create Guten Block. 

A preview of the “final” Testimonial Block in action

Building the block with Advanced Custom Fields

The first approach I want to look at is using Advanced Custom Fields. In my opinion, this is the quickest way to get up and running with building custom blocks for Gutenberg. It requires the least amount of code, in this case only PHP code and no custom JavaScript and provides some very nice features with minimal effort. 

Install Advanced Custom Fields

In order to build custom Gutenberg Blocks with Advanced Custom Fields, you will need version 5.8 or newer. At the time of writing this, v5.8 has not been released to the public, but is available to ACF Pro customers and can be downloaded from the ACF dashboard. The Gutenberg features are planned to be released to both the FREE and PRO versions of the ACF plugin. 

Once the plugin is installed, we can start building our custom block. 

Step 1: Register the Block

The first thing you’ll want to do is create a new plugin. To do that, create a new directory in your WordPress /wp-content/plugins/wp-content directory named gutenberg-testimonial-acf-example. 

In that directory, create a file named gutenberg-testimonial-acf-example.php and add the following code: 

<?php
/**
 * Plugin Name: Gutenberg Testimonial ACF Example
 */

Now we have a plugin that we can activate in our WordPress dashboard.

Next, add the following code to register your block with Advanced Custom Fields.

/**
 * Register the block once ACF has initialized
 */
add_action( 'acf/init', 'my_acf_init' );
function my_acf_init() {
 	// check function exists
	if ( function_exists( 'acf_register_block' ) ) {
 		acf_register_block( [
			'name'            => 'acf-testimonial',
			'title'           => __( 'Testimonial - ACF', 'gutenberg-testimonial-acf-example' ),
			'description'     => __( 'A custom testimonial block, using Advanced Custom Fields Pro.', 'gutenberg-testimonial-acf-example' ),
			'render_callback' => 'acf_testimonial_callback',
			'category'        => 'formatting',
			'icon'            => 'admin-comments',
			'keywords'        => [ 'testimonial', 'quote', 'acf' ],
		] );
	}
 }
 /**
 * Render Callback for the block. This is what is output in the Theme AND
 * in the preview within Gutenberg
 *
 * @param $block
 */
function acf_testimonial_callback( $block ) {
	?>
	<h2><?php echo $block['name']; ?></h2>
	<?php
} 

This code is registering a Gutenberg block using the acf_register_block() function. By registering our block with this function, we have a new Gutenberg Block that we can use in Gutenberg, and we have a new Block “location” that can be used to assign Field Groups to in ACF. We’ll take a look at that in a second.

You’ll notice that along with registering our block, we’ve also created a callback function that is called by our blocks render_callback

This callback works much like a shortcode callback, in that the Block attributes will be passed to this callback, and the callback will return the output for the block. In this case, we’re outputting an <h2> tag with the name of the Block. 

Gutenberg core uses the render_callback to render blocks in the theme, but you’re still responsible for creating a JavaScript implementation of your block to preview in the Editor. ACF makes use of the render_callback to preview your block in the Editor as well, allowing you to build custom blocks without writing any custom JavaScript.

Here’s a preview of the block at this stage:

Preview of the ACF Testimonial Block after initial registration of the block.

We can see the block can be added to the content of our post, and we see the title of the block in an <h2> tag both in our Editor and in our theme. . .but we can’t really do much with the block yet. 

Step 2: Write our markup and styles

In this next step, we’ll write the markup that we’ll use to output our block, and write some styles to go with it. 

Replace the render_callback() with the following:

function acf_testimonial_callback( $block ) {
	$block_id = $block['id'];
	$testimonial = 'This is the testimonial';
	$alignment = 'right';
	$avatar_url = 'https://placehold.it/100x100';
	$name = 'Cited Person';
	$text_color = 'white';
	$background_color = 'blue';
	?>
	<div id="<?php echo 'acf-testimonial-' . esc_attr( $block_id ); ?>" class="acf-testimonial">
		<div class="acf-testimonial-text">
			<?php echo esc_html( $testimonial ); ?>
		</div>
		<div class="acf-testimonial-info <?php echo esc_attr( $alignment ); ?>">
			<div class="acf-testimonial-avatar-wrap">
				<img src="<?php echo esc_url( $avatar_url ); ?>"/>
			</div>
			<h2 class="acf-testimonial-avatar-name">
				<?php echo esc_html( $name ); ?>
			</h2>
		</div>
	</div>
	<style>
		#<?php echo 'acf-testimonial-' . $block_id; ?>{
			background-color: <?php echo $background_color; ?>;
			color: <?php echo $text_color; ?>;
			padding: 30px;
		}
		#<?php echo 'acf-testimonial-' . $block_id; ?> h2{
			color: <?php echo $text_color; ?>;
		}
		.acf-testimonial-info{
			position: relative;
			display: inline-block;
			width: 100%;
			margin-top: 15px;
			min-height: 55px;
			padding-top: 5px;
			line-height: 1.4;
			text-align: left;
		}
		.acf-testimonial-info.right {
			text-align: right;
		}
		.acf-testimonial-avatar-wrap {
			position: absolute;
			left: 0;
			top: 0;
		}
		.acf-testimonial-info.right .acf-testimonial-avatar-wrap {
			right: 0;
		}
		.acf-testimonial-avatar-wrap img {
			max-width: 55px;
		}
		.acf-testimonial-avatar-name {
			color: #ffffff;
			margin-right: 81px;
			margin-left: 0;
			padding-left: 0;
			left: 0;
		}
	</style>
	<?php
} 

Here the first thing we do is define some variables that will eventually be dynamic values from our ACF Fields. Then we write some markup and a <style> tag to go with the markup, and we use our variables in the appropriate places to make our block dynamic. 

Note: you could/should move the static styles to an external stylesheet and enqueue, then use the <style> tag only for the dynamic styles. For brevity, I’ve included all styles, static and dynamic in the style tag.

At this point, our block is starting to take shape. We can add the block to our editor, and you can see that it has some structure and style to it and is consistent in the editor and our theme. . .but it’s still not editable.

Step 3: Register fields and make the block editable

Now that we have a block registered using acf_register_block() our block is now considered a valid location for adding Field groups to using ACF. 

Using the ACF UI, we can create a new field group and add the fields we’ll need and apply the field group to our block.

For this block, we know the dynamic values we want to edit, so we can create the following fields:

  • Testimonial: Textarea field for editing the testimonial text
  • Name: Text field for who citation of who said the testimonial
  • Avatar: Image field for uploading an image to the Testimonial
  • Alignment: Select field for choosing Right or Left alignment
  • Background Color: Color Picker field for selecting the background color
  • Text Color: Color Picker field for selecting the text color

Then, we can add our Field Group to our block, as it’s a valid Field Group Location because of acf_register_block(). See below:

Assigning fields to a block using ACF

You can also register your ACF Fields using PHP or JSON and keep your Fields versioned. In our case, we’ll use PHP. 

Add the following code to your plugin, right below where we registered our block.

if ( function_exists( 'acf_add_local_field_group' ) ) {
	acf_add_local_field_group( [
		'key'      => 'group_5bec5e04859ef',
		'title'    => 'ACF Avatar Block',
		'fields'   => [
			[
				'key'          => 'field_5bec5e12675e1',
				'label'        => 'Testimonial',
				'name'         => 'testimonial',
				'type'         => 'textarea',
				'instructions' => 'Enter the testimonial text',
				'required'     => 1,
			],
			[
				'key'          => 'field_5bec5f940015c',
				'label'        => 'Avatar',
				'name'         => 'avatar',
				'type'         => 'image',
				'instructions' => 'Select the image to use as the Avatar for the testimonial',
			],
			[
				'key'          => 'field_5bec5fb10015d',
				'label'        => 'Name',
				'name'         => 'name',
				'type'         => 'text',
				'instructions' => 'Enter the name to cite',
			],
			[
				'key'          => 'field_5bec5fb100166',
				'label'        => 'Background Color',
				'name'         => 'background_color',
				'type'         => 'color_picker',
				'instructions' => 'Set the Background Color',
			],
			[
				'key'          => 'field_5bec5fb100199',
				'label'        => 'Text Color',
				'name'         => 'text_color',
				'type'         => 'color_picker',
				'instructions' => 'Set the Font Color',
			],
			[
				'key'          => 'field_5bec5fcf0015e',
				'label'        => 'Alignment',
				'name'         => 'alignment',
				'type'         => 'select',
				'instructions' => 'Select the alignment',
				'choices'      => [
					'right' => 'Right',
					'left'  => 'Left',
				],
			],
		],
		'location' => [
			[
				[
					'param'    => 'block',
					'operator' => '==',
					'value'    => 'acf/acf-testimonial',
				],
			],
		],
		'active'   => 1,
	] );
}

This is registers the fields we need so we don’t need to use the ACF UI, and the location is set to our block. 

At this point, ACF has done the heavy lifting for us by wiring up our fields to our block. So now we can interact with our block and edit the fields we’ve defined. But we need to wire up the values of the fields to update our block in the render callback. 

We’ve already defined the variables, so now we just need to swap them with the field values. 

In your render callback, replace the variable definitions with the following:

$block_id         = $block['id'];
$testimonial      = get_field( 'testimonial' );
$alignment        = get_field( 'alignment' );
$avatar           = get_field( 'avatar' );
$avatar_url       = ! empty( $avatar['url'] ) ? $avatar['url'] : 'https://placehold.it/100x100';
$name             = get_field( 'name' );
$text_color       = get_field( 'text_color' );
$background_color = get_field( 'background_color' );

Here, instead of hard-coding our values, we use ACF to get the field values, using the ACF function get_field(). ACF does some magic to know the context of the block, so all we have to specify is the name of the field we want to get, and we’re good to go. The get_field() function is unique to the specific block, so you can have many blocks on one page each with their own dynamic values and get_field() will get the values for that specific block.

Now, with our fields registered and wired up, we have a fully interactive custom block, and haven’t written a single line of JavaScript. 

Building the same block with “Create Guten Block”

Create Guten Block is a fantastic project from an incredibly influential member of the WordPress – and greater Open Source – community Ahmad Awais.

In the same vein as Create React App, Create Guten Block is a tool that scaffolds out a project with pre-configured webpack setup optimized to reduce the friction needed to build Gutenberg blocks using React and ES6. You can read more about the project on Github: https://github.com/ahmadawais/create-guten-block

Step 1: Scaffold the block

Note: You’ll need the latest version of node/npm installed on your machine to work with Create Guten Block. 

The first thing you’ll want to do is create a new plugin. To do that, create a new directory in your WordPress /wp-content/plugins/wp-content directory named gutenberg-testimonial-cgb-example. 

In that directory, create a file named gutenberg-testimonial-cgb-example.php and add the following code: 

<?php
/**
 * Plugin Name: Gutenberg Testimonial Create Guten Block Example
 */

Now we have a plugin that we can activate in our WordPress dashboard.

From the plugin directory, in the command line, run the following command: npx create-guten-block testimonial-cgb

This will run the Create Guten Block script and will scaffold out a new plugin in the /testimonial-cgb directory. 

Step 2: Move scaffolded files to project root

We want the scaffolded files at the project root, so let’s move the files from the testimonial-cgb directory to the project root. 

Then add the following code to the root gutenberg-testimonial-cgb-example.php file and delete the plugin.php file

// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Block Initializer.
 */
require_once plugin_dir_path( __FILE__ ) . 'src/init.php';

Now, our plugin with a sample block is wired up and ready for us to start hacking on. From the command line, at the plugin root, run the following command to have Webpack start watching for changes: npm run start

Step 2.1: Understand what was scaffolded

At this point, we’ve got a plugin ready to go with an actual functional block. You can add it to a Gutenberg post right now. So, how does this work?

/src/init.php

At the root we are requiring the /src/init.php file, so let’s take a look at that. 

In this file, we’ve got 2 hooks executing 2 callback functions. 

The first hook is enqueue_block_assets and that executes the callback testimonial_cgb_cgb_block_assets().

This new hook enqueue_block_assets() is run anywhere Gutenberg is enabled, whether that’s the admin or the front-end of a Gutenberg-enabled post/page. 

In this case, we’re enqueueing just one stylesheet to be loaded in both the theme and the editor. This will allow us to share styles across the front and back end. 

You’ll note that we’re enqueuing a file /dist/blocks.style.build.css. This file is generated by the Create Guten Block webpack build whenever we run npm run build. It takes our .scss files and minifies them to a single CSS file that we can enqueue to WordPress. 

The next hook is enqueue_block_editor_assets. This executes the callback testimonial_cgb_cgb_editor_assets(), and that callback enqueues a JavaScript file and another CSS file. 

The enqueue_block_editor_assets hook is only executed in the context of the Gutenberg editor, so these are assets we need for the editing experience of Gutenberg, in our case any custom CSS we’ll need to style the editor and the JavaScript needed to interact with our block in the editor. 

Both files we’re enqueueing are “built” files generated by running npm run build. These are not the files we will use to edit directly.

/src/blocks.js

This is the entry point for the WebPack script to build our final distributable JavaScript from. So any JavaScript file we import here will be included in the final build. In our case, we’re importing just a single js file from /src/block/block.js. That is the block file we’ll be working with. You can build multiple blocks and import them to this entry point just like we’re importing this one block. 

/src/block/block.js

This is the main file we’ll be interacting with. Let’s move to the next step and you’ll get more familiar with this file as we go.

Step 3: Clean up scaffolded code

Create Guten Block scaffolds out the code with a lot of inline comments, which are great for context, but I want us to start in a clean spot with just the code, so let’s replace the /src/blocks/block.js file with the following:

import './style.scss';
import './editor.scss';

const { __ } = wp.i18n;
const { registerBlockType } = wp.blocks;

registerBlockType( 'cgb/testimonial', {
	title: __( 'Testimonial - CGB' ),
	icon: 'shield',
	category: 'common',
	keywords: [
		__( 'testimonial' ),
		__( 'create guten block Example' ),
		__( 'cgb' ),
	],
	edit: function( props ) {
		return (
			<h2>Testimonial CGB - Step 1</h2>
		);
	},
	save: function( props ) {
		return (
            <h2>Testimonial CGB - Step 1</h2>
		);
	},
} );

This will give us a very basic block we can start working with. 

What’s going on here?

At the top of the file, we’re importing the .scss files. This allows us to write our styles in SASS, but have WebPack compile them to CSS for us. 

Declare dependencies

Next, we declare what functions we’ll be using, and from where. 

const { __ } = wp.i18n;
const { registerBlockType } = wp.blocks;

Here, we’re saying we want to make use of the __ function and the registerBlockType function from wp.i18n and wp.blocks libraries respectively. 

If you look back in our init.php file, you will note that when we enqueued our Block editor JavaScript file, we declared some dependencies: 'wp-blocks', 'wp-i18n', 'wp-element'.

These are libraries provided by Gutenberg (soon to be WordPress core) that expose functions for us to make use of in our custom Gutenberg code. We need to declare that we want to use these functions and where they’re coming from before we make use of them, so that when webpack builds our final JS file, it builds in the proper order ensuring dependencies of one file are included before they’re being used. 

Register the block

Next we use registerBlockType to register the block. This function tells the Gutenberg application that a new block is available for use in the Gutenberg editor.

The key things to note here are the edit and save parts. When building blocks in JavaScript, we have an interface to edit the blocks, then we have to translate that into markup to be saved to the post’s content. What we save is what will be rendered by the theme, so we only want to save markup we plan to use in the output of the theme. So there tends to be some overlap between the Edit and Save methods, but the Edit method should provide extra functionality for interacting with the block in the editor.

In our case, currently, for save and edit we simply return an <h2> tag. 

At this point, our block looks like so:

(Screenshot says step 1, but it actually represents step 3.)

Step 4: Initial Markup and Styles

Let’s write our initial markup and styles so we can start seeing our Block come together. 

Let’s create a BlockContent component that will return our HTML structure (the same HTML we’re using in the ACF example). 

Below our const declarations, add the following: 

const BlockContent = () => {
    const testimonial = 'Testimonial...';
    const avatarUrl = 'https://placehold.it/55x55';
    const name = 'Citation Name';
    return(
	<div id="cgb-testimonial" className="cgb-testimonial">
	    <div className="cgb-testimonial-text">
		{testimonial}
	    </div>
	    <div className="cgb-testimonial-info">
		<div className="cgb-testimonial-avatar-wrap">
		    <img src={avatarUrl}/>
		</div>
		<h2 className="cgb-testimonial-avatar-name">
		    {name}
		</h2>
	    </div>
	</div>
    );
};

This is a functional Component that simply returns our Markup. 

Above the return statement, we declare some dynamic values testimonial, avatarUrl and name. These eventually will be fields that we will edit, but for now we’ve hard-coded their values. Then we output them in our markup using squiggly brackets, such as {testimonial}.

Next, let’s create an EditBlock component. For now, it will just return the <BlockContent />, but we’ll build on it shortly to provide interaction. 

Add this:

class EditBlock extends Component {
	render( ) {
		return [
			<BlockContent />
		];
	}
}

Now, update the edit part of our registerBlockType to be: edit: EditBlock, and update the save to be: 

save: function( props ) {
  return (
    <BlockContent />
  );
}

So, right now, our Edit and Save functions are both returning the exact same <BlockContent /> component, but we’re set up to start changing the editing and saving experience. 

At this point, the block should look like so: 

Example of the block with initial markup

Step 5: Define Attributes & Add Inspector Controls

This is a pretty big step, so instead of showing the code changes here, check out the DIFF on Github. 

I’ll explain a few parts here though.

Declare attributes

If there are any dynamic properties of a block we want to edit, we need to declare them. Gutenberg calls these dynamic properties “attributes”. 

In our case, we know we need the following dynamic properties to be editable: testimonial, avatarUrl, avatarId, name, background_color, text_color, and alignment.

We define those like so: 

attributes: {
  testimonial: {
   type: 'string',
   default: 'This is a testimonial'
  },
  avatarUrl: {
    type: 'string',
    default: 'https://placehold.it/100x100'
  },
  avatarId: {
    type: 'int',
    default: null,
  },
  name: {
    type: 'string',
    default: 'Citation Name'
  },
  background_color: {
    type: 'string',
    default: 'blue'
  },
  text_color: {
    type: 'string',
    default: 'white'
  },
  alignment: {
    type: 'string',
    default: 'left'
  }
},

Add Inspector Controls

Now, we want to add Inspector Controls to our block. In Gutenberg, when you select a block, on the right is a panel that allows you to modify block settings. This is referred to as the “Block Inspector”. 

We’re going to make use of existing Gutenberg-provided components to build out our Inspector controls. 

At the top of the file we add:

const {
    RichText,
    InspectorControls,
    PanelColorSettings,
    MediaUpload,
} = wp.editor;

const {
    Button,
    PanelBody,
    SelectControl,
} = wp.components;

This pulls in some components for us to use to build out our block settings. 

Next, we create our Inspector component like so:

class Inspector extends Component {
    constructor( props ) {
        super( ...arguments );
    }
	render() {
        const backgroundColors = [
            { color: '#00d1b2', name: 'teal' },
            { color: '#3373dc', name: 'royal blue' },
            { color: '#209cef', name: 'sky blue' },
            { color: '#22d25f', name: 'green' },
            { color: '#ffdd57', name: 'yellow' },
            { color: '#ff3860', name: 'pink' },
            { color: '#7941b6', name: 'purple' },
            { color: '#392F43', name: 'black' },
        ];
         const alignOptions = [
            { value: 'left', label: __( 'Left' ) },
            { value: 'right', label: __( 'Right' ) }
        ];
         const { setAttributes, attributes: { background_color, alignment, text_color }} = this.props;
         return(
            <InspectorControls key="inspector">
                <PanelBody>
                    <PanelColorSettings
                        title={ __( 'Background Color' ) }
                        initialOpen={ false }
                        colorSettings={ [ {
                            value: background_color,
                            colors: backgroundColors,
                            onChange: (value) => setAttributes({ background_color: value}),
                            label: __( 'Background Color' ),
                         } ] }
                    />
                    <PanelColorSettings
                        title={ __( 'Text Color' ) }
                        initialOpen={ false }
                        colorSettings={ [ {
                            value: text_color,
                            colors: backgroundColors,
                            onChange: (value) => setAttributes({ text_color: value}),
                            label: __( 'Text Color' ),
                         } ] }
                    />
                    <SelectControl
                        label={ __( 'Alignment' ) }
                        description={ __( 'Left or right align the cite name and title.' ) }
                        options={ alignOptions }
                        value={ alignment }
                        onChange={ ( value ) => this.props.setAttributes( { alignment: value } ) }
                    />
				</PanelBody>
			</InspectorControls>
		);
	}
}

There’s a lot going on here, so I’ll explain a bit. 

First we define the backgroundColors we want to be available in our color pickers, and we define the alignOptions we want to be available to our alignment Select option. 

Next, we pluck setAttributes and our actual attributes out of this.props for use in the component. 

In React, data passed down from one component to another are called ‘props’. So, in order for the Inspector to have access to setAttributes and the attributes, these will have to get passed to the <Inspector> component, and we’ll see where that happens later. 

Next, we return an instance of <InspectorControls> with our nested controls. 

I’d argue that it’s fairly easy to understand what’s happening here, even if you’re not a React expert, as it kind of reads like HTML now. We can see that inside our inspector we have a PanelBody, and inside that we have 2 instances of PanelColorSettings (color pickers for the background and text color) and a SelectControl for selecting the alignment option. 

Each of these controls has an onChange method, which passes the value through and calls setAttributes() where the new value of the field is saved by Gutenberg to the block. 

 Now, in our EditBlockContent component, we’ve also made some changes. I’ll let you digest some of them on your own, but want to point out one specific change. 

Our return statement returned an array previously. It still returns an array, but now the first thing it returns is <Inspector { ...{ setAttributes, ...this.props } } />,

This is the Inspector component we just built which includes all of our form controls for editing the block, and the next thing it returns is our actual editable block. 

So, we’re telling Gutenberg that we have multiple components at work here. We have the Inspector and the Block itself. So now, when users select our block to edit it, Gutenberg does the heavy lifting of showing the user our Inspector controls and our block in a selected/editable state. And here, we’re passing the props from the Block instance down to the Inspector, which is how the Inspector has the ability to access the attributes and call setAttributes() on behalf of the block.

At this point, we have a block that has Inspector controls that you can interact with, but our block still doesn’t respond to changes in the controls. We’ll wire that up next. 

Ugh. The Gif shows Step 4, but it’s really Step 5. . .

Step 6: Connect the block to our Inspector Controls

Now that we have a block and inspector controls, we need to wire it up to respect the changes of the inspector controls and react (pun intended) to the changes. 

We also are missing the ability to edit the testimonial text, citation text, and set the avatar image. We’ve opted to make those inline fields instead of Inspector fields (just to show another approach). 

The DIFF for this step is a lot to grok again, so take a look at the DIFF here and digest what you can. 

One thing to note, is that we’ve added a utility called classnames. You can add that yourself by running npm install classnames --save or by manually editing your package.json as seen in the Diff. 

We’ve also added an icons.js file which exports an SVG Icon we’re using in this step. 

So now, back in the block.js file we import classnames from 'classnames'; and import icons from './icons';. Note the first import isn’t relative, so it will come from our node_modules, but the second import is relative to our project. 

Next, if you look at our EditBlock component, it’s changed quite a bit. 

It’s replaced some of our otherwise “normal” HTML markup with some editable components. For example, our Testimonial is now a <RichText> component that is configured to change the testimonial attribute when it’s changed. This allows us to type inline in the block and have the testimonial change. We can also declare what kind of RichText features to support, such as bold, italic, strikethrough, etc. 

We use the RichText component again for the citation, and we use a <MediaUpload> component for selecting the avatar image. The MediaUpload component is set to modify the avatarUrl and avatarId attributes when we select an image from the media library.

Now, if we look at the save method, it’s changed quite a bit as well. 

At the top, we get the attributes we need from the props that the block passes down: 

const { attributes: { testimonial, avatarUrl, name, background_color, text_color, alignment }} = props;

Then we tell Gutenberg how we want the content to save. We were previously saving raw HTML with a few dynamic values, but that will create some funky formatting when we get the values from the RichText component, so instead we need to use <RichText.Content> to save our content.

Then we also make use of some dynamic style={{}} tags to apply the values of the color pickers to our dom nodes so the dynamic styles can be saved and rendered in our theme. 

At this point, our block is fully functional:

Conclusion

In my opinion, the developer experience provided by ACF is closer to what I would have expected the core Gutenberg developer experience to be. I believe that Gutenberg core should provide a lot of UI elements that can be used by sending up a description of blocks from a server side registry, Gutenberg reads that description of blocks, and renders the UI to interact with them. ACF provides this API. We didn’t have to write a single line of JavaScript and we had a fully functioning Gutenberg block.

The ACF integration is still young and there are limits to it, but it seems like it’s evolving very nicely, and I hope the Gutenberg core team pays attention and works toward an API that allows blocks to be created in a similar fashion where a server side API can be used to build blocks, and custom JS can be added only when the PHP API can’t get you the custom results you need. 

I’ve written quite a lot on why Gutenberg needs a server-side API here and here already, but we can certainly add improved Developer Experience to that list as well.

The Create Guten Block experience is not bad. . .at least speaking from the perspective of someone who’s done quite a bit of React work already and genuinely enjoys React development. . .If you’ve never used React or aren’t that comfortable with JS, I can’t imagine how frustrating it would be to navigate the ecosystem right now.

Even with a background in building React apps, building Gutenberg blocks does require a LOT of knowledge of JavaScript, React, certain historical decisions of the Gutenberg project, lightly/undocumented features of Gutenberg, es6 (you can write blocks in es5, but I wouldn’t recommend it), etc. There are certain ways in which Gutenberg deviates from standard React, so you have to learn and get used to some new conventions. 

We had to write a whole lot more code to get to essentially the same outcome as our ACF block, and in this case, the server has NO knowledge our block even exists, which I would argue is a bad direction for WordPress to head in. 

With the ACF approach, the server is fully aware of the block, so tools like WP-CLI, REST API, WPGraphQL, etc can expose data about the registered block and the block becomes immediately more useful than our client-only block. 

I do believe that building blocks on the client does empower developers to create more unique experiences so I don’t think I’ll ever argue that Gutenberg should not have a rich JavaScript API for building/extending blocks, but I definitely will continue to argue that the primary API for blocks should be a Server Side API, similar to what ACF (or Gutenberg Fields Middleware) are doing, where users can register blocks on the server, and that block registry is used to build the client representation without any JS needed. Then custom JS should be brought in for the situations where a generic API could not fulfill the blocks needs.  

Anyway, I hope this guide helps show some possibilities of Gutenberg and helps ya’ll understand some options for working with it. 

Posted by Jason Bahl

Senior WordPress Engineer at Digital First Media in Denver, CO. I love problem solving and working with WordPress. I'm the creator and maintainer of WPGraphQL. I'm passionate about Open Source software and the web. When I'm not writing code, you'll find me spending time with my beautiful wife Rachel and my 2 sons Andrew and Blake. I also enjoy playing and watching soccer.

7 Replies to “Two ways to build Gutenberg Blocks”

  1. “This is registers the fields we need so we don’t need to use the ACF UI, ..”

    Why not? Wouldn’t it be more flexible to use the ACF UI and require less code?

    Reply

    1. Using code instead of the UI allows for the field registration to be put into version control. It’s great that ACF allows for many ways to register fields, and for some sites using the UI might be the best option, but I prefer having the registration of the fields maintained in Git so that if they ever need to change the changes are tied to a specific commit in the code history, etc.

      Reply

  2. Nice comparison, enjoyed the post thoroughly. I still believe that moving more and more towards the JavaScript programming language would introduce much better dev/user experiences in the WordPress community.

    Also, thanks for all the kind words. I am hoping to push a major upgrade to create-guten-block next year. The version I am using for my projects is much more advanced and capable but it’s too tightly tied with my workflow. Hope to open source it bit by bit.

    Peace! ✌️

    Reply

    1. > I still believe that moving more and more towards the JavaScript programming language would introduce much better dev/user experiences in the WordPress community.

      I agree that WordPress and the community at large should continue to embrace JavaScript, but I still do believe that there’s a LOT of value the WordPress server can/should provide to Gutenberg that’s largely been ignored to this point in Gutenberg development.

      There are a lot of open issues on the Gutenberg repo regarding various issues that continue pointing more and more to the need for a good server-side API. For example, ASYNC loading of block components, etc. Right now, you need to enqueue ALL of the JS for ALL of the blocks whether they’re used on the page or not. Talk about a performance nightmare in the making. A good server-side API could help by only enqueuing scripts needed for the blocks actively being used and letting blocks enqueue their dependant scripts only when they’re interacted with.

      As Gutenberg moves to more parts of WordPress, we’ll find that we NEED a good server API for more things too, like validation of settings fields (and whatever else Gutenberg touches).

      Reply

  3. I’m stunned. I have not tried Gutemberg yet but your article is scary. In the “normal” version of WordPress, to create a paragraph of text with an image, I take 30 simple seconds. In Gutemberg do I need to know all these codes and add Custom Fields? Do I need to take a PHP or JavaScript programming course?

    Reply

    1. To use Gutenberg as an end user, writing content and building pages, you don’t need to write all this custom code. This post describes what it takes to code custom blocks. For many people, the core blocks that Gutenberg comes with might be sufficient to use, but for many projects custom blocks will be needed to be built and this sheds some light on some different ways to do build custom blocks.

      Reply

  4. “If you’ve never used React or aren’t that comfortable with JS, I can’t imagine how frustrating it would be to navigate the ecosystem right now.”

    Very true, unfortunately.

    I’ve stopped to count the hours and days wasted looking messages along the lines of “Module not found: Can’t resolve ‘@wordpress/components’ “, trying to decipher what is missing where and why and how-is-this-even-a-thing. What I was trying to do is something I’d still think of as rather simple and straight-forward: import the standard Gutenberg Columns block into CreateGutenBlock, and then add 3 variants to it.

    Doing something similar from scratch would have taken maybe two hours or three hours using ACF in the old PHP-based WordPress way.

    True, the user experience *may* be better in the end, but so far even that isn’t true. Using the unedited Columns block has resulted in many, many instanced of rebuilding pages in code view and even in the database, because Gutenberg Columns keep tripping over themselves – not to even mention the ridiculous dances one has to do to even get to the block.

    As to what Ahmad says “JavaScript programming language would introduce much better dev/user experiences in the WordPress community” – that’s probably true from a full-stack programmers view. However, it is my opinion that WordPress has also become as relevant as it is today not because things are programmed in the best possible way, but because it has been somewhat easy and accessible changing and creating stuff. Right now to me it seems WordPress is going the other way, a way a lot of high-tech CMS’s went, and remained rather niche products for it.

    Reply

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.