Using Block Templates with Gutenberg

Block templates are one of my favorite new features in Gutenberg. You can specify a list of blocks that automatically appear in the content editor, and you can customize what appears in each block by default.

You can also lock the template so no additional blocks can be added. This is a great replacement for metaboxes in many cases. You can control the content structure without sacrificing the visual editing experience. 

Block templates are currently set for an entire post type, but you will soon have more granular control to define them in page templates and other contexts.

Quick tip on implementation

I first build the page in the Gutenberg block editor. Then I add the code below to the theme, which prints out an escaped version of post_content. This shows me the blocks and parameters I need to build the block template.

I’m using ea_pp() below (code here) but you could also use print_r().

/**
 * Display Post Blocks 
 *
 */
function ea_display_post_blocks() {
	global $post;
	ea_pp( esc_html( $post->post_content ) );
}
add_action( 'wp_footer', 'ea_display_post_blocks' );

That helped me build the following block template:

'template' => array(
	array( 'core/heading', array( 'level' => 5, 'content' => 'Role' ) ),
	array( 'core/paragraph' ),
	array( 'core/heading', array( 'level' => 5, 'content' => 'Responsibilities' ) ),
	array( 'core/paragraph' ),
	array( 'core/heading', array( 'level' => 5, 'content' => 'Qualifications' ) ),
	array( 'core/list' ),
	array( 'core/heading', array( 'level' => 5, 'content' => 'Highlights' ) ),
	array( 'core/paragraph' ),
)

Which looks like this in the editor:

Post template with ads

Our client is a publisher who needs at least two ads in each post. There’s a few approaches we’ve used in the past:

  • Automatic insertion after X paragraphs. It’s simple to maintain but the ads often don’t follow the natural breaks in article.
  • Manual insertion using a shortcode. The content editor can ensure the ads work well with the content, but it’s more difficult to manage and easy to forget.

With Gutenberg, we simply pre-populate the content area with two ad blocks (built with Advanced Custom Fields) and three paragraph blocks. Content editors can then dive right into content creation around the ad units.

Here’s how you set the block template for posts:

/**
 * Block template for posts
 * @link https://www.billerickson.net/gutenberg-block-templates/
 *
*/
function be_post_block_template() {

  $post_type_object = get_post_type_object( 'post' );
  $post_type_object->template = array(
    array( 'core/paragraph' ),
    array( 'acf/ad' ),
    array( 'core/paragraph' ),
    array( 'acf/ad' ),
    array( 'core/paragraph' ),
  );
}
add_action( 'init', 'be_post_block_template' );

Testimonial block template

We feature testimonials throughout the website, and we have a Testimonial post type for managing them. In the past we would have disabled the editor and added a custom metabox on this post type to collect just the quote and byline.

With Gutenberg, we can limit the editor to just the “Quote” block. This allows the client to use the same UI for managing their blockquotes site-wide.

When registering your post type, use the template parameter to specify an array of which blocks should appear.

Including 'template_lock' => 'all' will prevent any changes to the layout. If you set 'template_lock' => 'insert' it will prevent new blocks from being inserted but still allow the writer re-arrange the existing blocks.

We’ll be featuring these quotes using the “Large” quote style in the theme, so I’ve added is-style-large to the block attributes.

<?php
/**
 * Testimonials
 *
 * @package      CoreFunctionality
 * @author       Bill Erickson
 * @since        1.0.0
 * @license      GPL-2.0+
**/

class EA_Testimonials {

	/**
	 * Initialize all the things
	 *
	 * @since 1.2.0
	 */
	function __construct() {

		// Actions
		add_action( 'init', array( $this, 'register_cpt' ) );
		add_filter( 'wp_insert_post_data', array( $this, 'set_testimonial_title' ), 99, 2 );
	}

	/**
	 * Register the custom post type
	 *
	 * @since 1.2.0
	 */
	function register_cpt() {

		$labels = array(
			'name'               => 'Testimonials',
			'singular_name'      => 'Testimonial',
			'add_new'            => 'Add New',
			'add_new_item'       => 'Add New Testimonial',
			'edit_item'          => 'Edit Testimonial',
			'new_item'           => 'New Testimonial',
			'view_item'          => 'View Testimonial',
			'search_items'       => 'Search Testimonials',
			'not_found'          => 'No Testimonials found',
			'not_found_in_trash' => 'No Testimonials found in Trash',
			'parent_item_colon'  => 'Parent Testimonial:',
			'menu_name'          => 'Testimonials',
		);

		$args = array(
			'labels'              => $labels,
			'hierarchical'        => true,
			'supports'            => array( 'editor' ),
			'public'              => true,
			'show_ui'             => true,
			'show_in_rest'        => true,
			'publicly_queryable'  => false,
			'exclude_from_search' => true,
			'has_archive'         => false,
			'query_var'           => true,
			'can_export'          => true,
			'rewrite'             => array( 'slug' => 'testimonial', 'with_front' => false ),
			'menu_icon'           => 'dashicons-format-quote',
			'template'            => array( array( 'core/quote', array( 'className' => 'is-style-large' ) ) ),
			'template_lock'      => 'all',
		);

		register_post_type( 'testimonial', $args );

	}


	/**
	 * Set testimonial title
	 *
	 */
	function set_testimonial_title( $data, $postarr ) {
		if( 'testimonial' == $data['post_type'] ) {
			$title = $this->get_citation( $data['post_content'] );
			if( empty( $title ) )
				$title = 'Testimonial ' . $postarr['ID'];
			$data['post_title'] = $title;
		}

		return $data;
	}

	/**
	 * Get Citation
	 *
	 */
	function get_citation( $content ) {
		$matches = array();
		$regex = '#<cite>(.*?)</cite>#';
		preg_match_all( $regex, $content, $matches );
		if( !empty( $matches ) && !empty( $matches[0] ) && !empty( $matches[0][0] ) )
			return strip_tags( $matches[0][0] );
	}
}
new EA_Testimonials();

There’s no need for writers to include a post title for these quotes, but WordPress’  auto-generated titles weren’t very descriptive.  I’m using the wp_insert_post_data filter to modify the post title to match the value in <cite>. If there is no byline, it sets the title to “Testimonial {ID}”

I don’t like parsing HTML for this data, but it works given the simple content structure. For more advanced layouts I recommend using something like Gutenberg Object Plugin to save the Gutenberg data as an array in the database so you can access it easier.

Nested Templates

You can create nested block templates using container blocks. For instance, here’s an example from the Templates section of the Gutenberg Handbook.

$template = array(
    array( 'core/paragraph', array(
        'placeholder' => 'Add a root-level paragraph',
    ) ),
    array( 'core/columns', array(), array(
        array( 'core/column', array(), array(
            array( 'core/image', array() ),
        ) ),
        array( 'core/column', array(), array(
            array( 'core/paragraph', array(
                'placeholder' => 'Add a inner paragraph'
            ) ),
        ) ),
    ) )
);

Bill Erickson

Bill Erickson is the co-founder and lead developer at CultivateWP, a WordPress agency focusing on high performance sites for web publishers.

About Me
Ready to upgrade your website?

I build custom WordPress websites that look great and are easy to manage.

Let's Talk

Reader Interactions

Comments are closed. Continue the conversation with me on Twitter: @billerickson

Comments

    • Bill Erickson says

      Metaboxes work exactly the same in WordPress 5.0 as they do in previous versions of WordPress. They will continue to play a role in WordPress for the foreseeable future.

      If you were using metaboxes for content creation because the Classic Editor lacked the features you need, you might be able to now accomplish those goals using the Block Editor instead of building custom metaboxes.

  1. Erik van der Bas says

    Mr Bill!

    Thanks a bunch fro your Gutenberg posts! I was wondering if you already know a method to implement block templates on a custom page template level. I can’t find anything documentation on it yet. Maybe you had more luck!

  2. Marcy Diaz says

    Thank you for all the info you’ve been sharing about Gutenberg and theme development.
    That first little function on this page is worth it’s weight in gold! Thank you!

  3. sushil says

    Hi,
    I am building a custom Gutenberg theme and wanted to create some custom blocks for it? is it possible to create custom block inside theme instead of creating a plugin? if yes may I please know how to do that?

      • sushil says

        Thanks for your prompt reply 🙂

        Actually the theme I am going to build will be distributed and used by many clients. So I am trying to find a way to build few custom blocks inside a theme. Ideally I don’t want to create a separate plugin to add few custom blocks to the theme.

        If I use ACF plugin do build custom blocks, will the theme and custom blocks work without the need to install ACF plugin on client sites?

        I am trying to figure this out from last few days but not able to find any answers till now. So I would greatly appreciate your help here.

        • Bill Erickson says

          Here’s an article on building blocks from scratch using JavaScript and React.

          I do not recommend packaging a custom block into a theme. When the user changes themes down the line they’ll lose access to that block and to the content created with it.

          All functionality a user expects to keep after changing themes (custom post types, metaboxes, custom blocks…) belongs in either a single core functionality plugin or multiple feature-specific plugins (ex: a plugin for each block).

          You can use TGM Activation or other tools to automatically install your plugin when the theme is activated.

  4. Dennis says

    Hi,

    Great article!

    What is the difference between block templates and reusable blocks? Is the only difference between those two constructs about whether it is tied to a specific post type or not?

    Thank you,
    Dennis

    • Bill Erickson says

      Block templates are like a starting point in content creation. It’s an initial arrangement of editable blocks.

      Reusable blocks are blocks with specific content in them already that you can reuse on multiple pages. If you edit the reusable block, it will change the content on every page that uses that reusable block.

      I’m using reusable blocks on Display Posts for a “PHP Warning” (example) and “CSS Warning” (example).

      Any time I insert a block of PHP code I type /PHP Warning and insert the same warning used on all other tutorials. If I decide to change that text, I can edit it once and it will update all the other tutorials that use it.

  5. Enrique says

    Hi Bill,

    Thanks a lot for your post. It is very helpful and interesting. I am trying to add some predefined blocks to my page, but i can’t get your method straight. Is there any chance that you could explain in which file to add which code?

    Thank you.

  6. Roberto Capannelli says

    Hi Bill,
    I can’t a find a proper official guide about the template array string structure like the one above for a custom block ( e.g. array( ‘acf/ad’ ) ).

    I followed the WordPress guide here -> https://wordpress.org/gutenberg/handbook/designers-developers/developers/tutorials/metabox/meta-block-5-finishing/
    to define a template but it doesn’t work and I think because it’s the wrong string I use (and the example uses of course).

    So I wonder if you know something specific about the structure that can I use?
    (I think its something about the folder plugin name or text domain even though the example doesn’t mention anything like that)

    Thanks in advance and for sharing this helpful post with all of us.
    Best regards

    • Bill Erickson says

      You use the block name, which is the first parameter of registerBlockType() when the block type was registered.

      Here’s a tutorial on Writing your first block type. If you wanted to include that block in your block template, you would use: gutenberg-boilerplate-es5/hello-world-step-01.

      As mentioned above in the quick tip, I’ve found it simpler to look at what’s in the $post->post_content field to get the name, rather than digging through plugin files to see what name they used when registering the block type.

  7. derin says

    Do I understand correctly that block templates are only loaded for the initial ‘create new post’ action, and once posted, will not respond to changes in the template function even with lock-all? I mean I tested it and this is the case but I was wondering if there is a way to make it where the blocks can be moved while maintaining their content in existing posts.

    • Bill Erickson says

      Correct, templates are a starting point for new content creation. They don’t affect existing content.

      • Patrik Illy says

        Thanks for great article. Do you know about solution where Gutenberg templates are editable for existing pages?

    • Chris says

      I’m guessing not, but did you find a way to move content around using the template once the post has already been published? Do you know if this is likely to become possible, Bill?

      • Bill Erickson says

        The template only applies to new content, and I don’t expect them to add support for existing content in the future. But I haven’t worked with locked down templates much – I only use block templates to pre-populate the editor with a recommended content structure.

        If you have a specific need not currently met by the templates, try posting it as an issue on the GitHub repo and see if it gets traction.