Creating a Custom Gutenberg Block in WordPress

#

Gutenberg: everyone’s talking about it. In fact, when Iain wrote about it recently, it quickly became our most-commented-on-post ever.

I have my own thoughts on the philosophy of focusing on the editor

Controversy aside, though, it’s actually a pretty cool tool and upgrade to the WordPress editing experience. I decided it was about time that I take a look and see what’s going on under the hood and how difficult it is to create a custom ‘block’. Iain’s post covered an intro to Gutenberg, so I won’t go into depth on what it is and what is does.

I’ll wait here if you haven’t read Iain’s post yet…

Getting started

We’re going to go over what it takes to go from nothing to a relatively basic, custom Gutenberg block. The documentation for Gutenberg is still kind of all over the place but there is some great tutorials and information if you get Googlin’. The gutenberg.news site is a pretty good resource as well.

What I used to get up and running quickly was the ‘create-guten-block’ tool by Ahmad Awais. It gives you a lot of the boilerplate stuff you need out of the box, like Webpack, ES2015 support etc. Setting it up is fairly straightforward, and it’s very similar to Create React App.

Once you’ve installed create-guten-block and run create-guten-block my-block you’ll have a plugin all set up and ready to customize with Gutenberg goodness. By default there’s not much there, but we’ll start customizing things right away.

To get up and running even faster, I used one of the example custom blocks available in the gutenberg-examples repo on Github. The recipe card example covers a lot of what you’ll want in a minimally interactive custom block.

We’re using the ‘ESNext‘ version of JavaScript here, but Gutenberg also supports ES5 style code as well.

Blocks

Ok, so what is a ‘Block’ anyway? I had a hard time understanding this concept when I first started working with Gutenberg.

From the documentation:

By embracing “the block”, we can potentially unify multiple different interfaces into one. Instead of learning how to write shortcodes, custom HTML, or paste URLs to embed, you should do with just learning the block, and all the pieces should fall in place.

Basically a ‘block’ is an organizational unit for editable ‘stuff’ in WordPress. At least that’s my take on it.

Blocks are the core building ‘block’, but how do you make one? Well, that part I can explain! Blocks are made almost entirely in JavaScript. Gutenberg brings a couple new actions (enqueue_block_assets and enqueue_block_editor_assets) to let you include your JavaScript and stylesheets to be used with Gutenberg.

function column_block_cgb_editor_assets(){
    // Scripts.
    wp_enqueue_script(
        'column_block-cgb-block-js', // Handle.
        plugins_url('/dist/blocks.build.js', dirname(__FILE__)),
        array('wp-blocks', 'wp-i18n', 'wp-element')
    );

    // Styles.
    wp_enqueue_style(
        'column_block-cgb-block-editor-css', // Handle.
        plugins_url('dist/blocks.editor.build.css', dirname(__FILE__)),
        array('wp-edit-blocks')
    );
} // End function column_block_cgb_editor_assets().

// Hook: Editor assets.
add_action('enqueue_block_editor_assets', 'column_block_cgb_editor_assets');

Luckily, create-guten-block sets these up for us already so we’re off to the races.

A world of JavaScript

If you were thinking there’d be more PHP from this point, think again! From here on in, we’re in a JavaScript world.

The key aspect to creating blocks in Gutenberg is the registerBlockType() function. It does all the work.

registerBlockType( 'my-block/cool-block-name', {
    // ... Massive JS object
}

And that’s it! See you next time!

Ok so, there’s a bit more to it than that, but at its core, this is how to create a Gutenberg block. In the ‘Massive JS object’ there’s a few things to take note of.

If you take a look in my-block/src/block.js you can see there’s a lot of config that goes into setting up a block. There are three main sections we’ll go over – attributes and the edit() and save() methods.

Attributes

If you want a block that actually does something, like allow you to edit text, you have to use Gutenberg’s state management system. That’s what the attributes object is. It’s pretty much identical to React’s state management concept, a top level object that keeps track of properties and what’s changed.

attributes: {
    title: {
        type: 'array',
        source: 'children',
        selector: 'h2',
    },
    mediaID: {
        type: 'number',
    },
    mediaURL: {
        type: 'string',
        source: 'attribute',
        selector: 'img',
        attribute: 'src',
    },
    body: {
        type: 'array',
        source: 'children',
        selector: '.callout-body',
    },
    alignment: {
        type: 'string',
    }
},

This is an attribute object I’m using to create a ‘callout’ block. The front end will have an image on one side and text on the other.

You can see that for each editable ‘thing’ in your block you need to define an attribute for it. There’s the mediaID and mediaURL for the image, values for a title and body content, as well as the overall alignment of everything. The edit() method takes these attributes as an argument so we can modify them in the editor interface.

edit()

The edit() function lets you customize the editing interface in Gutenberg. If you’re familiar with React’s render() function it’s pretty similar. You essentially specify a return statement that has your JSX in it.

edit: ( { attributes, className, isSelected, setAttributes } ) => {
    const { mediaID, mediaURL, body, alignment, title } = attributes;

    const onChangeTitle = value => {
        setAttributes( { title: value } );
    };

    const onSelectImage = media => {
        setAttributes( {
            mediaURL: media.url,
            mediaID: media.id,
        } );
    };

    const onChangeBody = value => {
        setAttributes( { body: value } );
    };

    const [ imageClasses, textClasses, wrapClass ] = sortOutCSSClasses( alignment, className );

    return [
        isSelected && (
            <BlockControls key="controls">
                <AlignmentToolbar
                    value={alignment}
                    onChange={( nextAlign ) => {
                        setAttributes( { alignment: nextAlign } );
                    }}
                />
            </BlockControls>
        ),
        <div className={wrapClass} key="container">
            <div className={imageClasses}>
                <div className="callout-image">
                    <MediaUpload
                        onSelect={onSelectImage}
                        type="image"
                        value={mediaID}
                        render={( { open } ) => (
                            <Button className={mediaID ? 'image-button' : 'button button-large'} onClick={open}>
                                {!mediaID ? __( 'Upload Image' ) : <img src={mediaURL} />}
                            </Button>
                        )}
                    />
                </div>
            </div>
            <div className={textClasses}>
                <RichText
                    tagName="h2"
                    placeholder={__( 'Write a callout title…' )}
                    value={title}
                    onChange={onChangeTitle}
                />
                <RichText
                    tagName="div"
                    multiline="p"
                    className="callout-body"
                    placeholder={__( 'Write the callout body' )}
                    value={body}
                    onChange={onChangeBody}
                />
            </div>
        </div>
    ];
},

You can see that the first thing we do is assign our attributes to some local variables to be used within the function.

const { mediaID, mediaURL, body, alignment, title } = attributes;

The above syntax is object destructuring and is mostly for the ‘less-typing-is-better’ effect. The next few functions are for handling the onChange() events in the editor. Basically when you change any value you want to update your application state. That’s what the setAttributes() method does.

const onSelectImage = media => {
    setAttributes( {
        mediaURL: media.url,
        mediaID: media.id,
    } );
};

The last part of the edit() function is the return statement. In here we have a bunch of JSX code that determines what the Gutenberg interface actually looks like. You can load in some standard components and blocks that are hooked onto the wp.blocks global variable.

const {
    registerBlockType,
    RichText,
    Editable,
    MediaUpload,
    BlockControls,
    AlignmentToolbar,
} = wp.blocks;

Then in your return statement you can use them and assign attribute values:

    return [
        ...
            <div className={textClasses}>
                <RichText
                    tagName="h2"
                    placeholder={__( 'Write a callout title…' )}
                    value={title}
                    onChange={onChangeTitle}
                />
                <RichText
                    tagName="div"
                    multiline="p"
                    className="callout-body"
                    placeholder={__( 'Write the callout body' )}
                    value={body}
                    onChange={onChangeBody}
                />
            </div>
        </div>
        ...
    ];

As of the latest release of Gutenberg, there’s a new block called RichText which replaces the older Editable block. This is the main block you’ll want to use to make editable areas in Gutenberg. There are options for setting the type of HTML tag it outputs as well as a variety of other settings. The onChange attribute is where you assign your onChange() function. This is important as it’s how you update your block attributes and make everything update in real-time.

save()

So that’s cool and all but we’re not actually saving anything yet. The next part of our custom block is the save() method.

This method defines how you want your block to be displayed on the front-end.

Gutenberg actually does something quite unique with how it saves data. Unless you’re saving data to post_meta the block configuration is serialized in an HTML comment above the block.

Gutenberg HTML comment

It’s a bit weird, but maintains backwards compatibility – if you ever disable Gutenberg the content will remain intact.

save: props => {
        const {
            className,
            attributes: {
                title,
                mediaURL,
                body,
                alignment
            }
        } = props;

    const [ imageClasses, textClasses, wrapClass ] = sortOutCSSClasses( alignment, className );

    return (
        <div className="bootstrap-block">
            <div className={wrapClass}>
                <div className={imageClasses}>
                    {
                        mediaURL && (
                            <img className="recipe-image" src={mediaURL} />
                        )
                    }
                </div>
                <div className={textClasses}>
                    <h2>
                        {title}
                    </h2>
                    <div className="callout-body">
                        {body}
                    </div>
                </div>
            </div>
        </div>
    );
}

The save() method is actually much simpler than the edit() method and you get the values of the attributes passed in as well. Then it’s mostly a matter of creating your front-end markup and inserting your values. Easy-peasy.

Issues I came across

Alright, so that’s the ‘how’ of creating a custom block, but what about the gotcha’s? Well, it turns out, there’s a few 😢.

The documentation is a little wacky at the moment. It’s not super easy to understand and is fairly general. I think as time goes on and Gutenberg is more stable this will change. But for now, there’s a lot of digging through code required.

Another thing I came across is that you can’t just use another ‘default’ block in your own custom block. Not every built-in block has an API or is exposed as a public class.

A real shortcoming I’ve found with Gutenberg over something like Beaver Builder is that multi-column layouts are not easy. There’s a columns block being introduced but it relies on nesting blocks together. It’s labeled as ‘experimental’ as it relies on CSS grid for layout.

I also found it really annoying working on a block that that’s actively changing in code. Every time you reload Gutenberg, you’ll get the “This block appears to have been modified externally…” message.

Gutenberg error

I get why it’s throwing the error, but it slows you down.

Another thing I’ve noticed is that the API is changing very quickly. Something that works today may not work tomorrow. That’s the nature of a project that isn’t ‘stable’ yet, but it’s something to keep in mind if you’re planning on implementing custom blocks for clients.

All this is to say: there are still some rough edges and it’s important to keep in mind Gutenberg is still very much in active development.

After it’s all said and done

Like Iain, I was pleasantly surprised (at first) by creating a Gutenberg block. One thing is for sure, though, is that it’s still very much ‘beta’ software and is changing every day. I think I’ll hold off on making any more custom blocks until the API is more solid and its features are complete.

One thing I would recommend before diving head first into creating a Gutenberg block, for now at least, is to get a solid understanding of React. If you’re not familiar with things like JSX you could use the ES5 syntax, but you’d be best suited to use the modern syntax in the long run.

Have you created a custom Gutenberg block yet? What was your impression? Let us know in the comments.

About the Author

Peter Tasker

Peter is a PHP and JavaScript developer from Ottawa, Ontario, Canada. In a previous life he worked for marketing and public relations agencies. Love's WordPress, dislikes FTP.