building gutenberg blocks tutorial

 

What is Gutenberg and Why is it Great?

Gutenberg is the soon to be released editor interface for creating content with WordPress, and many developers have already started building Gutenberg Blocks. The goal of the Gutenberg project is to unify the many ways content is currently created in WordPress and combine them into a consistent experience for the user. The Gutenberg editor puts the focus back on content and takes a drastically different approach to it than the traditional Rich-Text editor that ships currently with WordPress.

The concept behind Gutenberg is not new, it draws its inspiration from how old printing presses used a galley and chase to create reusable templates that could consistently reproduce the same layout. With its introduction comes the concept of composing content with blocks. Gutenberg Blocks function similar to the way most page-builder plugins currently work–by providing a way for users to visualize the layout and appearance of their content.

Gutenberg takes it a step further, built with React and Redux, it integrates deeply into WordPress to provide a rich and powerful new editor experience for authors. Its also friendly to plugin authors, exposing many APIs that make it easy to extend and create custom blocks. This article will discuss how to get started with developing a custom block and provide some useful tips and tricks for working with the core WordPress javascript API.

What are Gutenberg Blocks?

At a high level, a block is a template for part of your content, you can re-order them, copy and re-use them. Gutenberg Blocks allow you to embed rich media into your content and can be combined together to form the structure and layout of your post.

From a technical standpoint however, a block is a simple Javascript object that is comprised of the following properties:

  • title
  • icon
  • category
  • attributes
  • edit
  • save

Category

Title and icon are pretty self explanatory however, the remaining attributes that are more interesting. The category of a block determines where it will appear in the Add Block menu, options currently include: common, formatting, layout, widgets, embed.

Attributes

The attributes of a block represent the actual data that the block will manipulate. By default this metadata is serialized into JSON literal comments output in the post_content. Gutenberg also lets you to specify alternate sources such post meta or from the content itself. In addition to the name and source, data such as types and defaults can also be supplied for an attribute to assist with initialization and validation. The Attribute documentation outlines how to extract attributes when the block is regenerated.

Edit & Save

Both of these attributes are functions, the edit method returns a template that will render how the block will appear inside the WordPress editor. The save method returns a template that will render how the block will be actually output in the post_content.

Additional optional properties can be found in the Block API documentation.

Registering Custom Gutenberg Blocks

Gutenberg Blocks must be registered both from the front-end in Javascript and the backend in PHP. To register a block in on the Javascript side you must call registerBlockType function from the wp.blocks namespace and pass to it your block name prefixed by a namespace and your block object.

registerBlockType('my-plugin-namespace/my-block', {
  ...My Block Object
})

The process for registering the block server-side is a bit more involved. First you will also need to register all scripts and stylesheets required for your block

function register_block_editor_assets() {
  $dependencies = array(
    'wp-blocks',    // Provides useful functions and components for extending the editor
    'wp-i18n',      // Provides localization functions
    'wp-element',   // Provides React.Component
    'wp-components' // Provides many prebuilt components and controls
  );
  wp_register_script( 'my-block-editor', plugins_url( 'path/to/my/editor/script.js', __FILE__ ), $dependencies, VERSION );
  wp_register_style( 'my-block-editor', plugins_url( 'path/to/my/editor/style.css', __FILE__ ), null, VERSION );
}
add_action( 'admin_init', 'register_block_editor_assets' );

function register_block_assets() {
  wp_register_script( 'my-block', plugins_url( 'path/to/my/block/script.js', __FILE__ ), array( 'jquery' ), VERSION );
  wp_register_style( 'my-block', plugins_url( 'path/to/my/block/style.css', __FILE__ ), null, VERSION );
}
add_action( 'init', 'register_block_assets' );

Blocks are registered in PHP by calling the register_block_type function and passing to it your name spaced block name and an array of options.

register_block_type('my-plugin-namespace/my-block', array(
  'editor_script' => 'my-block-editor',
  'editor_style'  => 'my-block-editor',
  'script'        => 'my-block',
  'style'         => 'my-block'
));

In the block options you can pass in the handles of the scripts and stylesheets that you registered for your block. The editor prefixed script and styles will only be loaded when editing a post. The script and style arguments will be loaded both in the editor and the front-end of the site, here you can place css that is common to both contexts.

Creating a Basic Block

We will now demonstrate how to create a basic block with a few attributes. Please be advised that all following code snippets will be using ESNext syntax. Our block will have three attributes: one that is serialized; one that uses an attribute selector from the post_content; and one that uses post_meta.

We will begin by registering and stubbing out the shell of our block. Here I’ve chosen to use ES6 class syntax to represent our block. For now our edit and save methods will return just empty strings until we implement them in the next step.

class ExampleBlock {
  title = __('Example Block', 'example')
  icon = 'screenoptions'  
  category = 'common'
  attributes = {
    content: { // Parsed from HTML selector
      source: 'children',
      selector: 'p',
      type: 'array'
    },
    textColor: { // Serialized by default
      type: 'string'
    },
    isPinned: { // Pulled from REST API
      type: 'boolean',
      source: 'meta',
      meta: 'example_is_pinned'
    }
  }
  edit = ({ className, attributes, setAttributes, isSelected }) => ''
  save = ({ attributes }) => ''
}
registerBlockType('example/example-block', new ExampleBlock())

 

gutenberg example block
We should now be able to see our block in the Add Blocks menu

Editing and Saving Attributes

We will now go over how to implement the edit and save methods that will allow users to customize our block. In order to allow users to enter their content into the block we will use the RichText component found in the wp.blocks namespace.

The edit method will receive a props object with the following properties:

  • attributes – our block’s and their current values
  • setAttributes – a function for setting attributes
  • isSelected – a flag representing whether our block is currently selected in the editor
  • className – a custom class name property that exists on all blocks
edit = ({ attributes, setAttributes, className, isSelected }) => {
  const {
    content
  } = attributes
  return (
    <div className={className}>
      <RichText
        className="example-content"
        value={content}
        onChange={(content) =>setAttributes({ content })} />
    </div>
  )
})

Here we apply the className attribute to the wrapper div of our block. We also specify our content attribute as the value for our text input and in onChange, we call setAttributes to with our new content. We should now now be able to see text field where we can enter the content for our block.

gutenberg tinymce
RichText will embed a TinyMCE editor instance into our block

We have the beginnings on an editable UI for our block however it will not save our content quite just yet. When we defined our content attribute we specified that its value would be extracted from a paragraph tag in our block’s output. In our save method we now need to output the proper markup so that our content can be parsed when the block is regenerated.

Our save method will receive a props object with the following properties:

  • attributes
save = ({ attributes }) {
  const {
    content
  } = attributes
  return (
    <div>
      <p>{content}</p>
    </div>
  ) 
})

Here we also wrap our content in a paragraph tag so that it matches the selector that we specified when we defined the attribute above. We have also omitted the className because it is not required in the save method and will be automatically applied to our block when it is saved.

A Word On Block Validation

By now you may have ran into an error like this in your browser console

Block validation: Block validation failed for `example/example-block`
Expected:

<div class="wp-block-example-example-block"></div>

Actual:

<div class="wp-block-example-example-block">
  <p>Hello World!</p>
<div/div>

What this means is that the markup that Gutenberg generated your block, did not match the what was found in the post_content. When a block fails validation, Gutenberg will then prompt the user with two options, the Convert to Blocks option will convert the content of the block to a classic block as is and all attributes will be lost. The block will no longer be able to be edited with as its previous block type. The second option presented to the user is to manually edit the block’s HTML so that the can correct the block’s markup so that it matches what Gutenberg is expecting.

gutenberg convert to blocks
Recovery prompt for a block that has failed validation

The most common for this issue is when an an attribute has been removed. In development this is not a major cause for concern, however  it is advised to follow the steps outlined in the Deprecated Gutenberg Blocks documentation when removing attributes from blocks that in production.

Adding Controls to the Inspector Panel

Sometimes it won’t make sense for the control for an attribute to appear in the context of the editor content, we will look at how you can embed complex controls into the sidebar or what Gutenberg calls the Block Inspector. To have controls display in the inspector sidebar, we need to embed them into an InspectorControls component from the wp.blocks namespace. To keep our code clean, we will create a new method called renderInspector in our block class to output our inspector panel.

renderInspector = ({ isSelected, attributes, setAttributes }) => {
  if (!isSelected) {
    return null;
  }
  const {
    textColor
  } = attributes
  return (
    <InspectorControls>
      <div>
        <h2>{__('Text Color', 'example')}</h2>
        <BaseControl>
          <ColorPalette 
            value={textColor}
            onChange={(textColor) => setAttributes({ textColor })} />
        </BaseControl>
      </div>
    </InspectorControls>
  )
}

Gutenberg also comes with a built in ColorPalette control that provides a user friendly way to select the value of our textColor attribute. Our color picker has also been wrapped with a BaseControl component so that it that its top and bottom margins look consistent with the rest of the inspector. In our edit method, before the first child element we need to call our new renderInspector method and pass to it the parameters: attributes, setAttribute, and isSelected. The isSelected parameter will help us ensure that we only display the inspector when our block is being edited by the user.

return (
  <div className={className}>
    { this.renderInspector({ isSelected, attributes, setAttributes }) }
     // ... child elements
  </div>
)

You should now be able to see the text color palette in the inspector when your block is in focus.

gutenberg blocks color palette
The color palette provides a user friendly way to configure our text color attribute

While this works great under the hood to set the attribute, we need to provide a way for the user to visually see the color of their text change. This is where Gutenberg and React really shine, when the user selects a color from the palette, this will be reflected in the editor immediately. To do this, instead of manually applying the color to our text in both the edit and save methods, we will create a new React component that wraps our content and receives the text color as a prop.

Lets begin by creating a new file called component.js, this module will export a new component called Content. This component will simply wrap its children in a div element with a color style applied.

const Content = ({ children, textColor: color }) => <div style={{ color }}>{children}</div>

In our edit method we can now wrap this around our RichText component.

<Content textColor={textColor}>
  <RichText 
    className="example-content" 
    ... />
</Content>

And the paragraph element in our save method.

<Content textColor={textColor}>
  <p>{content}</p>
</Content>

In the editor you should now be able to see the color of your text change with the value you select in the inspector.

React allows Gutenberg to change the appearance of their blocks in realtime
React allows Gutenberg to change the appearance of their blocks in realtime

The inspector provides a useful alternative context to put controls for attributes that do not necessarily belong where the content is being edited by the user.

Post Meta and Gutenberg

Similar to the inspector panel, there is an additional context in which we can add controls that are more closely related to our content. This toolbar will automatically appear for some Gutenberg blocks if you are implementing something like Transforms. In this toolbar area we will be adding a button that will demonstrate a post_meta value that when toggled, adds a class to indicate if the post we are editing is a pinned post.

In order for our metadata accessible to Gutenberg, we will need to expose it in the REST API. To do this we simply have to call register_meta with our meta key and pass show_in_rest as an argument. This is all we have to do, WordPress has already taken care of the implementation for saving and retrieving post meta.

function register_pinned_meta() {
  register_meta( 'post', 'example_is_pinned', array(
    'show_in_rest' => true,
    'single'       => true,
    'type'         => 'boolean',
  ) );
}
add_action( 'init', 'example\register_pinned_meta' );

If you publish the post and navigate to {YOUR_WORDPRESS_INSTALL_URL}/wp-json/wp/v2/posts/{YOUR_POST_ID}, you should be able to find the meta key that we just added in the posts’ meta object.

{
  ...
  "slug": "1009",
  "status": "publish",
  "type": "post",
  "link": "http://localhost:8888/wordpress/2018/03/23/1009/",
  ...
  "meta": {
    "example_is_pinned": true
  },
...
}

I would highly recommend checking out the Considerations when using post meta for Gutenberg blocks. Additionally, here are a few caveats you should also be aware of when registering meta:

  • Meta will be registered for all post types–it is important to make your meta key unique by prefixing it so that it does not conflict with other plugins
  • Registered meta is public by default in the REST API for posts that have been published

Now to display our toolbar we will add another method to our class called renderToolbar. We will also need to use the BlockControls, Toolbar, and IconButton from the wp.components namespace.

 renderToolbar = ({ isSelected, attributes, setAttributes }) => {
  if (!isSelected) {
    return null
  }
  const {
    isPinned
  } = attributes
  return (
   <BlockControls>
     <Toolbar>
       <IconButton 
         className={classNames({
           'pin': true,
           'is-pinned': isPinned
          })}
          icon="admin-post"
          onClick={() => setAttributes({ isPinned: !isPinned })}
          tooltip={isPinned ? __('Unpin', 'example') : __('Pin This', 'example')} />
      </Toolbar>
    </BlockControls>
  )
}

We wrap our button in a Toolbar which is then wrapped with BlockControls, once again we check to make sure our block is selected before rendering our controls. When the button is clicked our isPinned attribute will be toggled and a conditional class is applied to the button so that we can add styles that give the user visual feedback of the attribute’s current state. Now we need to call our new renderToolbar method in the edit method of our block.

return (
  <div className={className}>
    { this.renderToolbar({ isSelected, attributes, setAttributes }) }
    { this.renderInspector({ isSelected, attributes, setAttributes }) }
    ...
  </div>
)

A pin icon should now appear above our block when it is focused

Post meta in Gutenberg

Finally, we will use our isPinned attribute in our save method to apply a conditional class that can be then styled by the theme

return (
  <div className={classNames({'is-pinned': isPinned })}>
      <Content textColor={textColor}>
        <p>{content}</p>
      </Content>
  </div>
)

gutenberg hello world 2
If we save our post and click preview we should see our text and the color that we set for it, and if we open our browser’s inspector tools we should see that the both our is-pinned and custom classes have been applied to the outermost div of our block.

gutenberg hello world block

Wrapping Up

We’ve talked a bit about what Gutenberg is and why it good for the future of WordPress. Gutenberg combines a lot of the concepts from more popular page builder plugins and presents them in a way that lets the end user focus on only what’s important when writing–the content–and it successfully presents in a way that is both extremely powerful and intuitive to the user.

The choice by the developers of Gutenberg to base the project on modern technologies like React and Redux extends to us the sort of flexibility that opens a door for nearly infinite possibilities for creating custom Gutenberg blocks that make our content richer and more interactive than ever before.

Seasoned WordPress developers have been creating content in pages and posts in many different ways, and while the introduction of Gutenberg will force a certain level of adaptation and change to the current way developers do thing, it will without doubt add a lot of flexibility, and consistency to the way we build things.

In this article we’ve covered over all of the steps from start to finish required to get started creating your own custom Gutenberg blocks and you can access the fully functional version of the plugin we built on our Github Repo. We also recommend that you checkout the WordPress Gutenberg Handbook on WordPress.org.


Click here to download the fully working Gutenberg block that we built in this tutorial.

Leave a Reply