How to build a Gutenberg block plugin

A step by step guide to creating your first Gutenberg block

Gutenberg is on its way. Whatever you think about the rights and wrongs of its implementation, it will undoubtedly offer more possibilities to people building WordPress sites. In this tutorial, I’m going to show you how to build a Gutenberg block plugin.

Contents

How to build a Gutenberg block plugin to display featured items

This is going to be the final output of our plugin:

Build Gutenberg block plugin

Feature blocks are a common feature on many, many websites. They’re used to provide key information about a product or service and will usually include an icon or image, title and text.

In our plugin, we’ll be able to add an icon or image, title and text and select whether to display one or two blocks per row.

In the standard WordPress editor, we had to insert this kind of content using a shortcode. With Gutenberg, we can implement it with a WYSIWYG interface.

You can find the source code for this plugin on Github or download it from the WordPress repo.

Setting up your plugin

The purpose of the tutorial is not to show you how to create a WordPress plugin from scratch so I’m going to skip over the basics. If you need more information, there are some tutorials listed here.

For this tutorial, let’s call our main plugin file feature-block-gutenberg.php. At the top of your file, you’re going to have something like:

<?php
/**
* Plugin Name: Gutenberg Feature Block
* Plugin URI: https://catapultthemes.com/
* Description: Add feature blocks using Gutenberg
* Author: Catapult Themes
* Author URI: https://catapultthemes.com/
* Version: 1.0.0
* License: GPL2+
* License URI: http://www.gnu.org/licenses/gpl-2.0.txt
*
* @package Gutenberg_Feature_Block
*/
// Exit if accessed directly.
defined('ABSPATH') || exit;

Enqueuing block assets

With the main file ready, we can start to think about enqueuing our plugin’s assets. If you are familiar with developing in WordPress, the first thing to note about Gutenberg is that development is mostly in JavaScript, not PHP.

With that in mind, we’ll need to enqueue the following assets:

  • block.js file – the file where we register and build the block itself
  • editor.css – the styles for the block in the editor
  • style.css – the styles for the block on the front-end

Unless there are differences in style between the editor and front-end, I recommend working on your styles in the style.css file. That file will be enqueued into both the front end and the editor. If you need to add any editor-specific styles, you can add them to the editor.css file.

In this tutorial, the editor.css file will actually be empty but I’m enqueuing it here anyway for the sake of completeness.

Our file structure

This plugin uses the following file structure:

Gutenberg block plugin file structure

You might want to get your folders and files set up ready. You can add empty files to begin with.

Gutenberg enqueue hooks

There are a couple of new hooks for enqueuing Gutenberg blocks:

  • enqueue_block_editor_assets – for editor assets
  • enqueue_block_assets – for front-end assets only

In the main plugin file, add the following:

function gfblock_enqueue_block_editor_assets() {
// Scripts.
wp_enqueue_script(
'gfblock-block', // Handle.
plugin_dir_url( __FILE__ ) . 'block/block.js', // File.
array( 'wp-blocks', 'wp-i18n', 'wp-element' ), // Dependencies.
filemtime( plugin_dir_path( __FILE__ ) . 'block/block.js' ) // filemtime — Gets file modification time.
);
// Styles.
wp_enqueue_style(
'gfblock-block-editor', // Handle.
plugin_dir_url( __FILE__ ) . 'assets/css/editor.css', // File.
array( 'wp-edit-blocks' ), // Dependency.
filemtime( plugin_dir_path( __FILE__ ) . 'assets/css/editor.css' ) // filemtime — Gets file modification time.
);
}
add_action( 'enqueue_block_editor_assets', 'gfblock_enqueue_block_editor_assets' );
/**
* Enqueue the block's assets for the frontend.
*
* @since 1.0.0
*/
function gfblock_enqueue_block_assets() {
wp_enqueue_style(
'gfblock-frontend', // Handle.
plugin_dir_url( __FILE__ ) . 'assets/css/style.css', // File.
array( 'wp-blocks' ), // Dependency.
filemtime( plugin_dir_path( __FILE__ ) . 'assets/css/style.css' ) // filemtime — Gets file modification time.
);
}
add_action( 'enqueue_block_assets', 'gfblock_enqueue_block_assets' );

Script dependencies

Note that the block.js file has some dependencies:

  • wp-blocks – this is the JavaScript library in Gutenberg that handles block registration and related functions
  • wp-element – the library that handles the block UI
  • wp-i18n – the internationalization library

Use filemtime for version

Also note the use of filemtime for the version parameter (filemtime gets the last modified time of the file). Often in plugins, we might just use the plugin version number here. Gutenberg needs the last modified time to ensure it has the most recent version of the file, otherwise updates you’ve made won’t be available when you try to preview or update a post.

Okay, that’s the basic plugin structure ready. Now we just need to start filling in the gaps.

The block.js file

The block.js file is where all the action happens. As I mentioned above, development in Gutenberg is through JavaScript. We start by making a wrapper for our code. I’m going to include some placeholder comments so that we can start dropping more code in as we progress.

In your block.js file, add:

( function( blocks, i18n, element ) {
/* Set up variables */
/* Register block type */
} )(
window.wp.blocks,
window.wp.i18n,
window.wp.element,
);
view raw block-js-wrapper.js hosted with ❤ by GitHub

Note that you are pulling through some global elements from the wp JavaScript object as outlined above, i.e. blocks, element and i18n. We can take a more detailed look at each of these before we start using them.

Gutenberg wp.blocks

The blocks library contains elements related to UI and functionality. This includes components like editable areas, media upload buttons, alignment toolbars and so on – clearly elements that will be useful in creating your block.

You can take a look at the blocks library on Github here. Click into the library folder to see all the existing blocks that ship with Gutenberg.

Gutenberg wp.element

The element directory is an abstraction layer on top of React.js – the JavaScript framework behind Gutenberg. React is out of scope for this tutorial, but you can read more about it on Github. In essence, element allows you to create elements in the DOM.

Gutenberg wp.i18n

This is possibly the most familiar concept to anyone who develops in WordPress: i18n deals with internationalization. We will see some examples of that in the code below. You can read more about it on Github here.

Setting up our plugin’s variables

The first thing we’re going to do in our block.js file is use the different elements we’ve pulled through from the global wp variable to create variables for our plugin.

In place of the comment /* Set up variables */ you can add the following code:

var el = element.createElement;
var children = blocks.source.children;
var BlockControls = wp.blocks.BlockControls;
var AlignmentToolbar = wp.blocks.AlignmentToolbar;
var MediaUpload = wp.blocks.MediaUpload;
var InspectorControls = wp.blocks.InspectorControls;
var TextControl = wp.blocks.InspectorControls.TextControl;
var SelectControl = wp.blocks.InspectorControls.SelectControl;

Let’s look at what each of these is going to accomplish in the plugin:

var el = element.createElement;

This imports createElement from React. We’ll use this to create our DOM elements, like div, img, p etc.

var BlockControls = wp.blocks.BlockControls;

BlockControls give us access to the controls toolbar that appears when our block is being edited:

Gutenberg control toolbar

var MediaUploadButton = wp.blocks.MediaUploadButton;

MediaUploadButton gives us access to a media upload button, allowing us to upload our image or icon via the standard WordPress media library.

var InspectorControls = wp.blocks.InspectorControls;

InspectorControls are additional setting controls for our block. These aren’t rendered in the controls toolbar but in the Inspector panel:

Gutenberg inspector panel

var SelectControl = wp.blocks.InspectorControls.SelectControl;

SelectControl gives us access to a select field that we can add to the inspector panel, as above.

With all our variables and dependencies set up, we can register our block type.

registerBlockType

The registerBlockType function implements your block’s content. For a fuller write-up, check out the Gutenberg handbook entry on writing your first block. For now, we’ll look at its main parameters. Add the following code to your block.js file instead of the /* Register block type */ placeholder.

blocks.registerBlockType('gfblocks/feature-block', {
title: i18n.__( 'Feature' ), // The title of our block.
icon: 'info', // Dashicon icon for our block
category: 'common', // The category of the block.
attributes: {}, /* Placeholder */
edit: {}, /* Placeholder */
save: {} /* Placeholder */
}

Firstly, notice that you need to specify the name of your block, including a namespace, in this case gfblocks/feature-block.

Secondly, note that you can add certain parameters as follows:

title: i18n.__( 'Feature' ),

Title is, obviously, the title of our block. Note that we have used the i18n declaration with the double underscore function to wrap our title. It’s not necessary to include a text domain.

Gutenberg title parameter

icon: 'info', // Dashicon icon for our block

The icon parameter allows us to specify an icon to accompany the title. Use the dashicons library for ease: just remove the dashicons- prefix from the icon you want to use to get its slug. If you want to use a custom icon, check out this article on using an svg icon.

category: 'common', // The category of the block.

Choose a category for your block. This can be one of the following:

  • common
  • formatting
  • layout
  • widget
  • embeds

The next line in our code is this:

attributes: {},

Which means it’s time to add some more code to our example.

Gutenberg block attributes

The attributes property is how Gutenberg remembers the content of a block and knows how to render it.

A typical attribute would be written like this:

uniqueKeyName: {
 type: 'string',
 source: 'attribute',
 selector: '.unique-class-name',
 attribute: 'src'
}

In this example, we’re creating an attribute with a unique name that will find the element with the specified class name, then extract the src attribute, and store that data as a string. Later on, when we look at rendering our block content, Gutenberg will use the selector attribute so it’s important that these are unique for each attribute.

There is more detail on Gutenberg attributes here.

Let’s add some attributes to our example. Add the following code to your block.js file instead of the attributes: {}, placeholder.

attributes: { // Necessary for saving block content.
titleOne: {
type: 'array',
source: 'children',
selector: '.gfblocks-title-1',
},
textOne: {
type: 'array',
source: 'children',
selector: '.gfblocks-text-1',
},
mediaIDOne: {
type: 'number',
},
mediaURLOne: {
type: 'string',
source: 'attribute',
selector: '.gfblocks-feature-image-1 img',
attribute: 'src',
},
hrefOne: {
type: 'url',
},
titleTwo: {
type: 'array',
source: 'children',
selector: '.gfblocks-title-2',
},
textTwo: {
type: 'array',
source: 'children',
selector: '.gfblocks-text-2',
},
mediaIDTwo: {
type: 'number',
},
mediaURLTwo: {
type: 'string',
source: 'attribute',
selector: '.gfblocks-feature-image-2 img',
attribute: 'src',
},
hrefTwo: {
type: 'url',
},
alignment: {
type: 'string',
default: 'center',
},
columns: {
type: 'select',
default: '2'
}
},

These attributes are the different pieces of data that our users will be able to enter when they’re editing the block, i.e.

  • titleOne – the title of the first feature
  • textOne – the text description for the first feature
  • mediaIDOne – the attachment ID for the first feature’s image
  • mediaURLOne – the URL to the first feature’s image

These attributes are repeated for the second feature. You can see how the attributes will be used by studying the screenshot below, showing a new instance of the block in the editor.

Gutenberg block editor

If you take a read through the attributes, you’ll notice several different arguments being used:

selector

You can specify an HTML element, e.g. div, or CSS selector, e.g. .my-selector-class, here. This argument is critical to how Gutenberg saves its data and how it knows what elements are editable.

source

The source argument defines how values are extracted from attributes. You could specify:

  • text – extract the text value
  • html – extract HTML
  • attribute – grab an attribute of an element, e.g. src of an img element
  • children – extract child nodes of an element as an array (we’ll see this in action below)
  • query – extract an array of attributes from an element

type

This is the type of data you are saving, e.g. string, url, integer, number, etc

[emphasis]Note that all the attributes data is made available via props.attributes – this is what we’ll use to get our data when rendering the block.[/emphasis]

The Gutenberg block edit function

With block attributes added, we can start to think about how the block is going to be rendered. The edit function describes how your block is structured with the editor, i.e. on the back-end.

Replace the edit: {}, placeholder with the following:

edit: function( props ) {
var focus = props.focus;
var focusedEditable = props.focus ? props.focus.editable || 'titleOne' : null;
var alignment = props.attributes.alignment;
var attributes = props.attributes;
var columns = props.attributes.columns;
/* Event handlers */
var onSelectImageOne = function( media ) {
return props.setAttributes( {
mediaURLOne: media.url,
mediaIDOne: media.id,
} );
};
var onSetHrefOne = ( value ) => {
props.setAttributes( {
hrefOne: value,
} );
};
var onSelectImageTwo = function( media ) {
return props.setAttributes( {
mediaURLTwo: media.url,
mediaIDTwo: media.id,
} );
};
var onSetHrefTwo = ( value ) => {
props.setAttributes( {
hrefTwo: value,
} );
};
function onChangeAlignment( newAlignment ) {
props.setAttributes( { alignment: newAlignment } );
}
function onChangeCols( newColumns ) {
props.setAttributes( { columns: newColumns } );
}
return [
!! focus && el( // Display controls when the block is clicked on.
blocks.BlockControls,
{ key: 'controls' },
el(
blocks.AlignmentToolbar,
{
value: alignment,
onChange: onChangeAlignment,
}
),
),
!! focus && el(
blocks.InspectorControls,
{ key: 'inspector' },
el( 'div', { className: 'components-block-description' }, // A brief description of our block in the inspector.
el( 'p', {}, i18n.__( 'Feature block options.' ) ),
),
el( 'h3', {}, i18n.__( 'Layout' ) ), // The number of columns.
el(
SelectControl,
{
type: 'number',
label: i18n.__( 'Number of Columns' ),
value: columns,
onChange: onChangeCols,
options: [
{ value: '1', label: i18n.__( 'One column' ) },
{ value: '2', label: i18n.__( 'Two columns' ) },
],
}
),
),
el( 'div', { className: props.className + ' gfblocks-cols-' + attributes.columns },
el( 'div', {
className: 'gfblocks-block gfblocks-block-1'
},
el( 'div', {
className: attributes.mediaIDOne ? 'gfblocks-feature-image gfblocks-feature-image-1 image-active' : 'gfblocks-feature-image gfblocks-feature-image-1 image-inactive',
},
el( blocks.MediaUpload, {
onSelect: onSelectImageOne,
type: 'image',
value: attributes.mediaIDOne,
render: function( obj ) {
return el( components.Button, {
className: attributes.mediaIDOne ? 'image-button-1' : 'components-button button button-large button-one',
onClick: obj.open
},
attributes.mediaIDOne ? el( 'img', { src: attributes.mediaURLOne } ) : i18n.__( 'Upload Image' )
);
}
})
),
el( 'div', {
className: 'gfblocks-feature-content gfblocks-feature-content-1', style: { textAlign: alignment } },
el( blocks.Editable, {
tagName: 'h3',
className: 'gfblocks-title-1',
inline: true,
placeholder: i18n.__( 'Feature Title 1' ),
value: attributes.titleOne,
onChange: function( newTitle ) {
props.setAttributes( { titleOne: newTitle } );
},
focus: focusedEditable === 'titleOne' ? focus : null,
onFocus: function( focus ) {
props.setFocus( _.extend( {}, focus, { editable: 'titleOne' } ) );
},
} ),
el( blocks.Editable, {
tagName: 'p',
className: 'gfblocks-text-1',
inline: true,
placeholder: i18n.__( 'Enter feature text...' ),
value: attributes.textOne,
onChange: function( newText ) {
props.setAttributes( { textOne: newText } );
},
focus: focusedEditable === 'textOne' ? focus : null,
onFocus: function( focus ) {
props.setFocus( _.extend( {}, focus, { editable: 'textOne' } ) );
},
} ),
),
),
el( 'div', {
className: 'gfblocks-block gfblocks-block-2'
},
el( 'div', {
className: attributes.mediaIDTwo ? 'gfblocks-feature-image gfblocks-feature-image-2 image-active' : 'gfblocks-feature-image gfblocks-feature-image-2 image-inactive',
},
el( blocks.MediaUpload, {
onSelect: onSelectImageTwo,
type: 'image',
value: attributes.mediaIDTwo,
render: function( obj ) {
return el( components.Button, {
className: attributes.mediaIDTwo ? 'image-button-2' : 'components-button button button-large button-two',
onClick: obj.open
},
attributes.mediaIDTwo ? el( 'img', { src: attributes.mediaURLTwo } ) : i18n.__( 'Upload Image' )
);
}
}),
),
el( 'div', {
className: 'gfblocks-feature-content gfblocks-feature-content-2', style: { textAlign: alignment } },
el( blocks.Editable, {
tagName: 'h3',
className: 'gfblocks-title-2',
inline: false,
placeholder: i18n.__( 'Feature Title 2' ),
value: attributes.titleTwo,
onChange: function( newTitle ) {
props.setAttributes( { titleTwo: newTitle } );
},
focus: focusedEditable === 'titleTwo' ? focus : null,
onFocus: function( focus ) {
props.setFocus( _.extend( {}, focus, { editable: 'titleTwo' } ) );
},
} ),
el( blocks.Editable, {
tagName: 'p',
className: 'gfblocks-text-2',
inline: true,
placeholder: i18n.__( 'Enter feature text...' ),
value: attributes.textTwo,
onChange: function( newText ) {
props.setAttributes( { textTwo: newText } );
},
focus: focusedEditable === 'textTwo' ? focus : null,
onFocus: function( focus ) {
props.setFocus( _.extend( {}, focus, { editable: 'textTwo' } ) );
},
} ),
),
),
)
];
},

This is the longest section of code in the plugin. We’ll look at it in four parts:

Setting variables and event handlers

The first few lines of this section include some variables – mainly just grabbing attributes of the props argument passed into the function for ease of use later.

The event handlers allow us to change the value of attributes whenever an element is edited, e.g. passing in new values for the image URL and ID when an image is uploaded.

Gutenberg BlockControls

The following section of code extends the default toolbar that appears whenever a block is being edited.

}
return [
!! focus && el( // Display controls when the block is clicked on.
blocks.BlockControls,
{ key: 'controls' },
el(
blocks.AlignmentToolbar,
{
value: alignment,
onChange: onChangeAlignment,
}

We first check to see if the block has focus (otherwise we don’t display the toolbar). We then extend the default toolbar by adding the AlignmentToolbar that we imported from wp.blocks at the top of the file.

We use the onChangeAlignment event handler that we defined earlier for when the alignment element is clicked.

Gutenberg InspectorControls

We saw above that we can use InspectorControls to add controls to the inspector panel in the sidebar. We can see from this snippet of code how we are including a select field in the inspector panel to allow us to choose between a one and two columns layout.

!! focus && el(
blocks.InspectorControls,
{ key: 'inspector' },
el( 'div', { className: 'components-block-description' }, // A brief description of our block in the inspector.
el( 'p', {}, i18n.__( 'Feature block options.' ) ),
),
el( 'h3', {}, i18n.__( 'Layout' ) ), // The number of columns.
el(
SelectControl,
{
type: 'number',
label: i18n.__( 'Number of Columns' ),
value: columns,
onChange: onChangeCols,
options: [
{ value: '1', label: i18n.__( 'One column' ) },
{ value: '2', label: i18n.__( 'Two columns' ) },
],
}
),
),

We create elements using el (see below) for the panel heading and description then use SelectControl to create the dropdown list.

We use the onChangeCols event handler to assign the value of the select field, either 1 or 2, to props.attributes.columns. We’ll retrieve that later.

Creating elements

Remember el – the variable we created as a shorthand for createElement? We use this to start creating elements on our page. The rendered HTML is going to take this structure:

<div class="wp-block-gfblocks-feature-block gfblocks-cols-2">
<div class="gfblocks-block gfblocks-block-1">
<div class="gfblocks-feature-image gfblocks-feature-image-1"><img src="http://localhost/catapult_themes/gutenberg/wp-content/uploads/sites/14/2018/01/01-01.png"></div>
<div class="gfblocks-feature-content gfblocks-feature-content-1" style="text-align:center">
<h3 class="gfblocks-title-1">Rockets</h3>
<p class="gfblocks-text-1">Because every feature icon block ever has a rocket ship. You can upload your own image, of course.</p>
</div>
</div>
<div class="gfblocks-block gfblocks-block-2">
<div class="gfblocks-feature-image gfblocks-feature-image-2"><img src="http://localhost/catapult_themes/gutenberg/wp-content/uploads/sites/14/2018/01/01-03.png"></div>
<div class="gfblocks-feature-content gfblocks-feature-content-2" style="text-align:center">
<h3 class="gfblocks-title-2">Balloons</h3>
<p class="gfblocks-text-2">Because sometimes it's nice to float away too. Use Feature Blocks to let people know about your stuff.</p>
</div>
</div>
</div>

So we use el first to create the wrapper div. We create a dynamic class name using the value of attributes.columns in order to manipulate the layout depending on whether we are displaying one or two columns.

Then we use el again to create the div with the class gfblocks-block gfblocks-block-1 and we continue by creating a button element, an H3 element, and a p element. We repeat the whole thing for the second feature.

Let’s look in detail at the H3 element:

el( blocks.Editable, {
tagName: 'h3',
className: 'gfblocks-title-1',
inline: true,
placeholder: i18n.__( 'Feature Title 1' ),
value: attributes.titleOne,
onChange: function( newTitle ) {
props.setAttributes( { titleOne: newTitle } );
},
focus: focusedEditable === 'titleOne' ? focus : null,
onFocus: function( focus ) {
props.setFocus( _.extend( {}, focus, { editable: 'titleOne' } ) );
},
} ),

This has a number of parameters:

  • tagName – specify the HTML tag that will wrap this element
  • className – this is critical, it should match the selector you used when specifying the attributes above
  • inline – true or false
  • placeholder – use the i18n function to specify translatable placeholder text
  • value – uses the value of the attribute that you’ve specified earlier to populate the field with the correct value
  • onChange – what to do when the value of the field changes
  • focus – whether this element has the focus
  • onFocus – what to do when the element gains the focus

The key thing here is to ensure your class name is unique and that it matches the selector you used in defining the attributes earlier. Gutenberg uses this selector to map content.

The Gutenberg block save function

The save function defines the way that block attributes are rendered on the front-end.

save: function( props ) {
var attributes = props.attributes;
var alignment = props.attributes.alignment;
return (
el( 'div', { className: props.className + ' gfblocks-cols-' + attributes.columns },
el( 'div', {
className: 'gfblocks-block gfblocks-block-1'
},
attributes.mediaURLOne &&
el( 'div', { className: 'gfblocks-feature-image gfblocks-feature-image-1', style: {} },
el( 'img', { src: attributes.mediaURLOne } ),
),
el( 'div', { className: 'gfblocks-feature-content gfblocks-feature-content-1', style: { textAlign: attributes.alignment } },
el( 'h3', { className: 'gfblocks-title-1' }, attributes.titleOne ),
el( 'p', { className: 'gfblocks-text-1' }, attributes.textOne ),
),
),
)
);
},

In many ways, this is similar to the edit function. We’re using el to create our elements then we’re populating them with the values of the attributes.

Block CSS

I mentioned at the start of the article that there are two CSS files enqueued: style.css is enqueued in the editor and on the front-end and editor.css is enqueued in the editor only. Because there is no extra CSS necessary in the front-end, I’ve placed all the styles in style.css.

.wp-block-gfblocks-feature-block {
position: relative;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-flex-direction: row;
-ms-flex-direction: row;
flex-direction: row;
-webkit-flex-wrap: wrap;
-ms-flex-wrap: wrap;
flex-wrap: wrap;
}
.gfblocks-block {
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-flex-direction: column;
-ms-flex-direction: column;
flex-direction: column;
width: 100%;
padding: 1rem;
}
.wp-block-gfblocks-feature-block.gfblocks-cols-2 .gfblocks-block {
-webkit-flex: 0 1 50%;
-ms-flex: 0 1 50%;
flex: 0 1 50%;
}
.wp-block-gfblocks-feature-block .gfblocks-feature-image {
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-justify-content: center;
justify-content: center;
}
.gfblocks-feature-image img {
width: auto;
max-height: 150px;
}
.wp-block-gfblocks-feature-block.gfblocks-cols-1 .gfblocks-block-2 {
display: none;
visibility: hidden;
}
.wp-block-gfblocks-feature-block .gfblocks-feature-content {
padding: 0 1rem;
box-sizing: border-box;
}
.wp-block-gfblocks-feature-block .gfblocks-feature-image button {
margin: 0 auto;
}
.wp-block-gfblocks-feature-block .gfblocks-feature-image button.image-button {
line-height: 0;
padding: 0px;
}
.wp-block-gfblocks-feature-block .gfblocks-feature-image button.image-button img {
margin: 0;
}

Typical Gutenberg error messages

At the time of writing, Gutenberg 2.0 has just been released, and there are a couple of common error messages you might encounter while developing your blocks.

This block appears to have been modified externally. Overwrite the external changes or Convert to Classic or Custom HTML to keep your changes.

Gutenberg error message

I encountered this error whenever the block validation failed – for instance, if I’d updated the block and got the attribute selectors mismatched. It’s possible to view the page in the page inspector where you’ll find some more information on the failure.

This block has encountered an error and cannot be previewed

Gutenberg generic error message

This error message is very generic. Most commonly, I found that I encountered this error when I’d made an error in the JS – mistyping a variable name, for instance. Again, you’ll find more information in the page inspector.

I anticipate that error messages might become more specific further down the line.

Resources for Gutenberg developers

It’s early days yet and no doubt the number of resources available will start multiplying exponentially. I found the following to be useful:

Finally

If you are building a plugin or theme, using Gutenberg or not, you should consider Wisdom which gives you unparalleled stats on your product’s usage and users.

8 thoughts on “How to build a Gutenberg block plugin

Great resource, Gareth! I’ve just added this to gutenberg.news. Perhaps you could add our site to your list of resources in the post above? Thanks in advance!

Reply

Hi Gareth
Thanks for your tutorial.
I have install your plugin and I have one question : Is it possible to delete an image.
I mean it’s possible to delete the block or to replace one image by another but is it possible to delete an image of the block ?

Reply

Hi Nicolas

Good question – I will look into that and update.

Gareth

Reply

Your article is by far the best tutorial I’ve found until now on gutenberg. 😀
I just have one question and it’s about array type . I notice you use it to get text like with textTwo.
Is it possible to use it like a javascript array. I mean to get array of letter [“M”, “T”, “W”, “R”, “F”, “S”, “S”] or
number [12, 19, 3, 17, 28, 24, 7] ?

Reply

Thanks Luka. I haven’t tried with an array – please try and let me know.

I have some other updates to make at some point so will try myself then.

Reply

Didn’t seem to work with array
I have try with “Saab”, “Volvo”, “BMW” it gives me value:
Array[1]
0:
“\”Saab\”, \”Volvo\”, \”BMW\””

Reply

Leave a Reply

Your email address will not be published. Required fields are marked *