If there's one and only one feature that made WordPress gain 30% of the web's market share, this feature would be Extensibility. What is WordPress without Yoast? What is WordPress without ACF? Without JetPack? WooCommerce? Without Akismet? Without Contact Form 7? What is WordPress without Hello Dolly? It's nothing but another niche CMS with way less than its current market share.

So yes, Extensibility is key, and we can't build the next generation of the WordPress Editor without making it extensible. What does "making it extensible" mean? Does this mean providing hooks and filters in JavaScript the same way we do in PHP? Does this mean allowing plugins to extend and tweak any UI component used by Gutenberg? Does this mean adding filters to the REST API? Does this mean Giving access to an Editor object to manipulate the editor's content?

There's no clear answer to all these questions. Extensibility is a very difficult problem and unless you build it with real use-cases in mind, you're more likely to get it wrong. (and being stuck with it for a long time).

So let's take a look a closer look at Extensibility in Gutenberg and what we can achieve as a third party plugin right now.

Creating blocks

As you may have heard, Gutenberg is about blocks (or content blocks) you combine to compose content. A block that can represent any visual element: A heading, a paragraph, an image, an embed, a table, a list, a form, a dynamic list of posts, …

You can think of blocks as shortcodes v2 with a better UI to add and modify them. Blocks can be static (just rendering some static HTML) or dynamic (changing over time and rendered server side).

If the editor is made up of blocks, the most natural way to extend it is by creating blocks.

1- Create a static block

So our first example will be to add a new static block we'll call it "Alert" displaying an alert message, The alert can have three states (warning, error or success) and has a message.

This block could look like this (minus the control to switch the type, just to make it simpler):

// myblock.js

var el = wp.element.createElement,
	registerBlockType = wp.blocks.registerBlockType,
	html = wp.blocks.source.html;

registerBlockType( 'riad/alert', {
	title: 'Alert Block',
	icon: 'warning',
	category: 'common',

	attributes: {
		type: {
			type: 'string',
			default: 'danger',
		},
		message: {
			type: 'string',
			source: html( 'div' )
		},
	},

	edit: function( props ) {
		var className = props.className;
		var type = props.attributes.type;
		var message = props.attributes.message;
		function updateMessage( event ) {
			props.setAttributes( { message: event.target.value } );
		}

		return el(
			'p', 
			{ className: className + ' alert-' + type },
			el(
				'textarea',
				{ value: message, onChange: updateMessage }
			) 
		);
	},

	save: function( props ) {
		var = props.attributes.type;
		var message = props.attributes.message;


		return el(
			'p', 
			{ className: 'alert-' + type },
			message
		);
	},
} );

Provide some styles like this

/* myblock.css */

.wp-block-riad-alert.alert-danger { color: red; }
.wp-block-riad-alert.alert-success { color: green; }
.wp-block-riad-alert.alert-warning { color: orange; }

And load this block in my plugin

<?php
// plugin.php

function riad_enqueue_blocks() {
	wp_enqueue_script(
		'riad-block',
		plugins_url( 'block.js', __FILE__ ),
		array( 'wp-blocks', 'wp-element' )
	);
}
add_action( 'enqueue_block_editor_assets', 'riad_enqueue_blocks' );

function riad_enqueue_styles() {
	wp_enqueue_style(
		'riad-block-style',
		plugins_url( 'myblock.css', __FILE__ ),
		array( 'wp-blocks' ),
		filemtime( plugin_dir_path( __FILE__ ) . 'myblock.css' )
	);
}
add_action( 'enqueue_block_assets', 'riad_enqueue_styles' );

So what's happening here:

  • A javascript file to register the block providing some general information (an icon, a category, and a title),
    an edit
    function representing the visual state of the block in the editor and a save function representing the HTML output by this block,
  • A stylesheet adding some default styles to this block, extendable by themes,
  • And just a bunch of classic plugin hooks to tie everything together.

I won't go too deep into the details here, if this is the way you want to extend Gutenberg, I encourage you to take a look at the Gutenberg Docs as this is heavily addressed.

2- Create a dynamic block

The second kind of blocks you can add to Gutenberg is the dynamic blocks. Let's say we want to highlight the last published post in several other pages/posts. To do so we'll create a dynamic block showing the title and a link to the latest post.

// myblock.js

var el = wp.element.createElement,
	registerBlockType = wp.blocks.registerBlockType,
	withAPIData = wp.components.withAPIData;

registerBlockType( 'riad/latest-post', {
	title: 'Latest Post',
	icon: 'megaphone',
	category: 'widgets',

	edit: withAPIData( function() {
		return {
			posts: '/wp/v2/posts?per_page=1'
		};
	} )( function( props ) {
		if ( ! props.posts.data ) {
			return "loading !";
		}
		if ( props.posts.data.length === 0 ) {
			return "No posts";
		}
		var className = props.className;
		var post = props.posts.data[ 0 ];
		
		return el(
			'a', 
			{ className: className, href: post.link },
			post.title.rendered
		);
	} ),

	save: function() {
		// Rendering in PHP
		return null;
	},
} );

And add the server-side rendering like so

<?php
// block.php

function riad_render_block_latest_post( $attribites ) {
	$recent_posts = wp_get_recent_posts( array(
		'numberposts' => 1,
		'post_status' => 'publish',
	) );
	if ( count( $recent_posts ) === 0 ) {
		return 'No posts';
	}
	$post = $recent_posts[ 0 ];
	$post_id = $post['ID'];
	return sprintf(
		'<a class="wp-block-riad-latest-post" href="%1$s">%2$s</a>',
		esc_url( get_permalink( $post_id ) ),
		esc_html( get_the_title( $post_id ) )
	);
}

register_block_type( 'riad/latest-post', array(
	'render_callback' => 'riad_render_block_latest_post',
) );

And that's it! Notice:

  • The edit function still shows a representation of the block in the editor's context (this could be very different from the rendered version, it's up to the block's author)
  • The save
    function just
    returns null
    because the rendering is performed server-side.
  • The server-side rendering is a function taking the block attributes as an argument and returning the markup (quite similar to shortcodes)

3- Saving to Post Meta

"Ok! now I get it, creating blocks is cool, it replaces static and dynamic shortcodes, but what about
metaboxes"

Metaboxes are used for everything in the current WordPress's editor. One of these use-cases (the most frequent one) is adding a custom post meta.

Blocks address this use case quite nicely by allowing plugin authors to declare meta attributes, block attributes retrieved and saved to a post meta:

// myblock.js

var el = wp.element.createElement,
	registerBlockType = wp.blocks.registerBlockType;

registerBlockType( 'riad/book-title', {
	title: 'Book Title',
	icon: 'book',
	category: 'common',

	attributes: {
		title: {
			type: 'string',
			meta: 'book-title'
		},
	}

	edit: function( props ) {
		var className = props.className;
		var title = props.attributes.title;
		function updateTitle( event ) {
			props.setAttributes( { title: event.target.value } );
		}

		return el(
			'p', 
			{ className: className },
			el(
				'textarea',
				{ value: title, onChange: updateTitle }
			) 
		);
	},

	save: function() {
		return null;
	},
} );

Due to some REST API limitations, you also have to register the custom meta to your post type.

function gutenberg_my_block_init() {
	register_meta( 'post', 'book-title', array(
		'show_in_rest' => true,
		'single' => true,
	) );
}
add_action( 'init', 'gutenberg_my_block_init' );

Done! we have a block saving a book title to a custom post meta.

Removing Blocks

Adding blocks is easy enough, but one of the concerns we hear a lot from agencies about is that Gutenberg gives a lot of power to their clients and they won't be able to control what markup their users will produce using Gutenberg.

I do think this concern is irrelevant. Gutenberg offers exactly that: a way to have a controlled set of blocks. We can't mess up with the post content as easily as in the current editor.

1- Black List

Either way, if you think a block is irrelevant for you or for a specific block type, just unregister it. For example, if I don't like the verse block because I'm not a poet, I can just remove it like so:

// myplugin.js

wp.blocks.unregisterBlockType( 'core/verse' );

Load my script in Gutenberg

<?php
// myplugin.php

function riad_blacklist_blocks() {
	wp_enqueue_script(
		'riad-blacklist',
		plugins_url( 'myplugin.js', __FILE__ ),
		array( 'wp-blocks' )
	);
}
add_action( 'enqueue_block_editor_assets', 'riad_blacklist_blocks' );

Easy enough! right!

2- White list

Some people do want finer control over the allowed blocks, let's update our code above to use a whitelist instead:

// myplugin.js
var allowedBlocks = [
  'core/paragraph',
  'core/image',
  'core/html',
  'core/freeform'
];

wp.blocks.getBlockTypes().forEach( function( blockType ) {
  if ( allowedBlocks.indexOf( blockType.name ) === -1 ) {
    wp.blocks.unregisterBlockType( blockType.name );
  }
} );

Modifying blocks

Since we can register and unregister a block, this also means, to modify an existing block we can unregister it, modify it and register it back.

1- Changing a block's title

Say I don't like the "paragraph" block's title, I want it to be "text", I can do it:

// extend-block.js

var paragraphBlock = wp.blocks.unregisterBlockType( 'core/paragraph' );
paragraphBlock.title = "Text";
wp.blocks.registerBlockType( 'core/paragraph', paragraphBlock );

2- Adding a caption

Now that you got the principal, let's do bigger modifications. What if we want to add a "caption" to the "code" block. To achieve this, we can "decorate"
the edit
, save and attributes like so:

// extend-block.js

var el = wp.element.createElement;
var Editable = wp.blocks.Editable;
var codeBlock = wp.blocks.unregisterBlockType( 'core/code' );
codeBlock.attributes.caption = wp.blocks.source.children( '.my-caption' );
var OriginalBlockEdit = codeBlock.edit;
var OriginalBlockSave = codeBlock.save;
codeBlock.edit = function( props ) {
  return [
    el( OriginalBlockEdit, Object.assign( props, { key: 'original' } ) ),
    el( Editable, {
      key: 'caption',
      className: 'my-caption',
      value: props.attributes.caption,
      onChange: function( value ) {
        props.setAttributes( { caption: value } );
      },
    } ),
  ];
};
codeBlock.save = function( props ) {
  return [
    el( OriginalBlockSave, Object.assign( props, { key: 'original' } ) ),
    el( 'div', {
      key: 'caption',
      className: 'my-caption',
    }, props.attributes.caption ),
  ];
};
wp.blocks.registerBlockType( 'core/code', codeBlock );

With this technique, you can imagine enhancing any existing Block easily.

3- Transforming blocks

Gutenberg has a built-in transformations API allowing third-party plugins to create blocks and define how these blocks could be transformed to (or from) any other block including core blocks.

As an example, let's add a transformation to the "alert" block we created above to allow turning it into a regular paragraph block

// myblock.js

registerBlockType( 'riad/alert', {
  // ...
  transforms: {
    to: {
      type: 'block',
      blocks: [ 'core/paragraph' ],
      transform: function( attrs ) => {
        return wp.blocks.createBlock( 'riad/paragraph', {
          content: attrs.message,
        } );
      },
    },
  },
} );

The transformations API is not documented yet because it's still subject to change but it's a powerful API, it allows way more than this. Some of the use-cases covered by this API are:

  • Paste transformations: Transforming pasted content into a specific block depending on the pasted content
  • Patterns transformations: Allow shortcuts: For example, typing `# ` in an empty paragraph creates a heading block, this behavior can be extended to any other prefix and block.
  • Drag and Drop transformations: Drag and dropping a PDF file into the editor creates a custom PDF block

Styling

1- Styling blocks

One of the most frequent questions we're being asked is how Gutenberg affects existing themes and how can theme authors enhance the Gutenberg Experience.

Blocks come with unopinionated styles built-in. This makes basically any WordPress theme out there compatible with Gutenberg without any change.

However, to give them more personality themes could easily tweak the styling of any blocks and Gutenberg makes this easy by having a well-defined output markup for each block and a generated class name for most blocks.

Let's say I want to give the
pullquote block a specific design in my theme, It's just a matter of styling the `wp-block-pullquote` className

/* theme.css */

.wp-block-pullquote {
  background: #EEE;
  border: none;
}

2- Styling the editor

But what If I want to mimic a frontend styles and make the editor show blocks exactly how my theme would do it? To achieve this, just isolate the content's specific styles in a stylesheet blocks.css loaded by both the frontend's pages and the editor and load this stylesheet in the Gutenberg's editor like so:

// functions.php

function my_theme_editor_styles() {
    wp_enqueue_style( 'theme-blocks-style', get_template_directory_uri() . '/blocks.css');
}
add_action( 'enqueue_block_editor_assets', 'my_theme_editor_styles' );

Theme Support

Themes can also extend Gutenberg using the "theme support" API. For now, two options are available: provide a color palette and enable wide alignments.

Several block types make use of "colors" and to ensure a
seemless experience across blocks, define a color palette like so:

// functions.php

function mytheme_setup_theme_supported_features() {
  add_theme_support( 'gutenberg', array(
   'colors' => array(
     '#a156b4',
     '#d0a5db',
     '#eee',
     '#444',
   ),
  ) );
}

add_action( 'after_setup_theme', 'mytheme_setup_theme_supported_features' );

What's next?

When extending Gutenberg, the first question you need to ask yourself is: Could this be done within a block? In most cases, the answer to this question is YES. But this doesn't mean Gutenberg won't allow developers to extend other UI elements.

Let's take a look at the different extension APIs being considered in Gutenberg.

1- Extending blocks

As I showed you earlier, it's possible to modify existing blocks by unregistering and registering them with a modified version. In the future, registering core blocks by third-party plugins may be prohibited and a simpler API to extend blocks will be provided.

2- Slots

Internally, Gutenberg makes an extensive use of "Slots". Slots are pluggable UI areas. In the future, Gutenberg will more likely expose those slots to third-party plugins to allow extending areas like the Sidebar and the header allowing use-cases like:

  • Adding a panel to the post settings sidebar.
  • Adding a button to the editor's header.
  • Adding an item to the editor's mode switcher
    dropdown.

3- Decorators

While slots are great to enable adding items to the UI, they don't allow to modify existing UI components. Gutenberg is built using reusable components and one way to extend the behavior of several UI elements of Gutenberg could be to modify these usable elements. Decorators are a generic pattern explored in Gutenberg allowing extensions to any component. Decorators can be used to allow the following use-cases:

  • Extend the `Editable` component to add "footnotes" to all text-based blocks
  • Extend the publish button and change it's background if the post content is not HTML valid (or any other check)
  • Remove some components by extending them with "empty" components.

4- Other higher-level extensions

In addition to handling blocks (adding, removing, extending), Gutenberg will more likely allow more higher-level extensions API like registering new block categories, extending the text patterns

5- Block Templates

This is one of my favorite considered extension API, block templates. Block templates are a list of pre-filled blocks with placeholders, some of these blocks are "locked", they can't be moved or removed. Imagine a "product" custom post type defining a template with an image block, a text block for the description, a price block. And when a user decides to add a new product, the editor is already prefilled with these blocks. All
what's left is filling the placeholders. Isn't this a great experience for Custom Post Types?

Conclusion

Building extensibility APIs is hard, and replicating the actions and filters we're using in PHP plugins may not be the best option for longer-term viability.  Relying on real use-cases and building generic patterns around them is what would lead to the best possible extensibility API for Gutenberg and the future of JavaScript-based WordPress plugins.

If you're a plugin/theme developer, I encourage you to try the tips exposed in this post and share your extensibility use-cases with the Gutenberg team by replying to existing issues or creating new ones.

Thanks Grzegorz Ziółkowski for the feedback


This post is brought to you by the new Gutenberg Editor 

2 thoughts on “ One thousand and one way to extend Gutenberg today ”

Leave a Reply