Creating custom Gutenberg Blocks using ACF 5.8.0-beta2

Gutenberg is a new content editor for WordPress coming in the soon-to-be-released Version 5.0. Central to the Gutenberg editing experience is the concept of 'blocks'.

Blocks are Gutenberg's way of itemising content and functionality into individual components, so instead of one single stream of content in the familiar TinyMCE editor, you will be able to use multiple blocks of different types to compose a page or post.

It comes with various block types out of the box, but it's pretty clear that creating custom block types is going to become a common task for WordPress theme developers.

Lately I've been building most of my themes using ACF Pro's Flexible content fields. This allows me to achieve a similar componentized, page building type of experience, while maintaining the seperation between the data that the CMS user creates, and the template code that surrounds it.

Unfortunately, from what I've seen so far when reading about creating custom blocks, using Gutenberg's API seems to encourage the opposite. That is, creating the DOM structure for your custom block (including CSS classes), as part of the block itself, which is all stored in the database as content.

I prefer having template code abstracted away from the data itself, especially if that data is ever going to be used outside of the theme or accessed over a REST API.

Until I learn more about a better way to create Gutenberg blocks using the official Gutenberg API, it looks like I'll have to stick to a meta field solution.

Fortunately ACF have already been working on an integration with Gutenberg, which can be used to create custom blocks, and their approach maintains that separation of data and template code.

How to create a custom Gutenberg block with ACF 5.8.0-beta2

Note: This was tested with a specific beta version of the ACF plugin and WP v4.9.8, it is possible, perhaps likely, that the API will change before Gutenberg's official release in 5.0.0. It's also worth reading the post on the ACF website for more information: ACF 5.8 – Introducing ACF Blocks for Gutenberg

For this test I've set up a new WordPress 4.9.8 install with a basic child theme of the TwentySeventeen theme, where I'll be creating the custom blocks. I've installed and activated the Gutenberg plugin, and ACF 5.8.0-beta2, which you can download from the my-account section on the ACF website if you've purchased ACF Pro.

Step 1 - Register a block

So to start we're going to create a folder in our child theme called 'blocks' and a file inside that for registering our custom blocks, mine is 'register_blocks.php'.

Now the file exists, let's include it from functions.php so that it runs:

functions.php:

// Custom Gutenberg block registration
require_once get_stylesheet_directory() . '/blocks/register_blocks.php';

Now let's register a custom block using a new ACF function:

blocks/register_blocks.php:

add_action( 'acf/init', 'custom_block_registration' );
function custom_block_registration() {
	// If we have access to the new 'acf_register_block' function
	if( function_exists( 'acf_register_block' ) ) {
		// Register our custom block type
		acf_register_block( array(
			'name'				=> 'my-test-block',
			'title'				=> 'Test block',
			'description'		=> 'A custom testing block.',
			'render_callback'	=> 'custom_block_render_callback',
			'category'			=> 'formatting',
			'icon'				=> 'admin-comments',
			'keywords'			=> array( 'test' ),
		) );
	}
}

This code runs on the 'acf/init' hook, and first checks for access to the acf_register_block function, before using it to register our custom block.

Most of the arguments here are self explanatory. We need to give the block a title and description, for instance. We also need to give the block a hyphenated name, (if we use underscores here they're converted to hyphens automatically anyway), a render callback which we'll use to render the block template, and some Gutenberg meta like icon, and category, which determine the dashicon used in the editor and which tab the button for the block will be in.

Step 2 - Call a template

In our block registration we set a render_callback, which is a function that calls a template like so:

blocks/register_blocks.php:

function custom_block_render_callback( $block ) {
	// Remove "acf/" from name so we can use a path-friendly slug
	$slug = str_replace( 'acf/', '', $block['name'] );
	
	// include a template part from within the "template-parts/block" folder
	if( file_exists( STYLESHEETPATH . "/blocks/templates/{$slug}.php" ) ) {
		include( STYLESHEETPATH . "/blocks/templates/{$slug}.php" );
	}
}

As you can see I've set up a /templates directory inside /blocks to house the custom block templates. Once the first part of the function runs (which removes an ACF namespace from the block name), the rest calls in a file with the same name we used when registering the block.

Step 3 - Set up an ACF field group

On the Custom Fields admin page, we can create a field group, and a couple of fields. In my case I just created title and subtitle text fields.

Image of ACF fields

Then at the bottom in the Location section, we can find a new option for Block in the select dropdown:

ACF Location selector

And once selected, we should also see the custom block we registered as an option in the second dropdown:

ACF Block selector

Once these are selected, publish the field group and head over to Posts to create a test.

Let's create a new post, and now we can click on 'Add block' and we can find our custom block called 'Test block' under the 'formatting' section, (set in the 'category' argument in the acf_register_block function). Select it and the block will be added to the page.

Gutenberg Block selection

Now the block should show and we can fill in our fields that we created with some text.

Custom Gutenberg Block in editor

Step 4 - Create a template

Create a file called my-test-block.php inside /templates, this will be our template for the block.

In my example, I'm just going to output the title and subtitle data and a couple of CSS classes like so:

blocks/templates/my-test-block.php:

<?php
/**
 * Custom block template: Test Block
*/

// create align class ("alignwide") from block setting ("wide")
$align_class = $block['align'] ? ' align' . $block['align'] : '';
?>

<div class="my-test-block <?php echo $block['className'] . $align_class; ?>">
	<h1><?php echo get_field( 'title' ); ?></h1>
	<h2><?php echo get_field( 'subtitle' ); ?></h2>
</div>

So in this basic template, we're accessing the data in $block to output CSS classes for the 'Additional CSS class' and alignment options that can be added in the editor, and using get_field to get the field data for this block.

Check the front end of the post you created earlier, and you should be able to see the output for the custom block.

Custom Gutenberg Block working

Summary

That's it! Now we have a custom block that we built using ACF and PHP templates.

I can think of many reasons why someone might want to build blocks this way. Here are a few off the top of my head:

  • Continue using ACF for custom meta fields which can be properly integrated into Gutenberg-constructed pages
  • Build custom Gutenberg blocks without interfacing with Gutenberg's React API (which could be intimidating for some WP devs if they mostly come from a PHP background)
  • Build custom blocks using PHP with access to WordPress and PHP functions
  • Build custom blocks that don't pollute the database with DOM structure

This should by no means be seen as an opportunity to avoid learning JavaScript deeply if you're a WordPress developer. It's been a contentious year for the Gutenberg project but whether you agree with its direction or not, JavaScript is clearly becoming a big part of the future of WordPress and web development more generally.

For sure I have my own concerns about Gutenberg as it stands today, however I am also curious to see where it leads and hope that it matures and improves over time. For now however, I'm thrilled that ACF is working on an alternate API for creating custom blocks that will no doubt make a lot of people's lives a lot easier in the short term.