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 a freelance WordPress developer and a contributing developer to the Genesis framework. For the past 14 years he has worked with attorneys, publishers, corporations, and non-profits, building custom websites tailored to their needs and goals.

Ready to upgrade your website?

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

Let's Talk

Reader Interactions

Comments

  1. OpenBayou says

    Hello,

    Thanks for writing this. Is there a way to have a block show if based on a post format? For example: if a post format is audio, then a block is show.

    Something like:

    ‘if ( has_post_format(‘audio’, $post_id)) {
    array( ‘acf/settings’ ),
    }’

    Thanks.

  2. Stanislav says

    It can be hard to find resources on Gutenberg sometimes, so thank you for this very informative post. I’ll be using block templates it in my next project for sure!

  3. David says

    When I leave the editor and open the post again I am getting the following errors on the heading blocks ….”This Block contains unexpected or invalid content”.

    When I look at the code it seems to remove the level attribute..and is ony showing .

    I can click “resolve” and “convert to blocks”, and it will put the level attribute back in. Just wondering if there is a fix for this…this can be tedious if i have 100+ posts.

    Thanks

    DavidHello

    I have created a custom type post and loaded it with a gutenberg block template. I used your example on this page:

    ‘template’ => array(
    array( ‘core/heading’, array( ‘level’ => ‘5’, ‘content’ => ‘Role’ ) ),
    array( ‘core/paragraph’ ),
    array( ‘core/heading’, array( ‘level’ => ‘5’, ‘content’ => ‘Responsibilities’ ) ),
    )

    When I add a new custom type post, it loads the block template properly and displays the post without issues. The code editor also shows the proper header block and attribute as follows:

    When I leave the editor and open the post again I am getting the following errors on the heading blocks ….”This Block contains unexpected or invalid content”.

    When I look at the code it seems to remove the level attribute..and is ony showing .

    I can click “resolve” and “convert to blocks”, and it will put the level attribute back in. Just wondering if there is a fix for this…this can be tedious if i have 100+ posts.

    Thanks

    David

    • Bill Erickson says

      Hmm, that’s really strange. I wonder if there’s a plugin or theme conflict? Something might be filtering or modifying the post content and breaking the HTML comments.

      I’ve never seen that specific issue before.

      • Sam says

        This had me going round in circles given the terrible gutenberg documentation – heading ‘level’ has to be a number (ie. ‘level’ => 5 ).
        ‘level => ‘5’ sets it to a string which half works: it sets the block, but doesn’t update the context sidebar leading to this strange conflict between the block you set and some sort of saved default.

        Firefox dev tools gives the following nonsense warning message:

        > Block validation: Expected tag name `h2`, instead saw `h3`.

        and an error

        > Content generated by `save` function: heading text Content retrieved from post body: heading texter

  4. Antal says

    Hello Bill! Earlier you’ve mentioned that the ad blocks are custom blocks that execute a custom function. It seems to me a great idea, to have custom blocks inserted into the content that could be used to have dynamic content.
    So if I want to insert something after the 3rd paragraph I wouldn’t have to use the content filter, instead having a block sit there, which pulls the data from somewhere: for example from a meta field or from a custom post type post called for example “After 3rd paragraph”. And if the meta/linked post type has content it would be inserted and if not it just stays empty.

    For example, how could I link a php function to be executed in a custom block (without ACF pro)?:

    (here post 123 is a post that only serves to contain something to be displayed after the 3rd paragprah of each post that has the 3rd paragraph block)

    function custom_block_after_third_paragraph_function() {
    $content_for_third_paragraph_to_display = get_post(123);
    $content = $content_for_third_paragraph_to_display->post_content;
    if($content != “”){
    echo $content;
    }
    }

    Then I could create and include in the template some blocks that could be helpful to avoid filtering the content each time I would like to insert some temporary content after certain paragraph but I couldn’t wrap my head around how to create these php function executing blocks. And it would also open the possibility to create dynamic blocks, which sounds very useful.

    Also is there a way to get the content of certain blocks by name or ID to be echoed like a custom field?
    So for example instead:
    echo get_field(‘field name’);
    Would it be possible to do something like:
    echo get_block(‘block name’)?

    I’ve found some solution in your other article (https://www.billerickson.net/access-gutenberg-block-data/), but parsing each block and then assigning them into a variable one by one like this:

    function be_display_post_blockquote() {
    global $post;
    $blocks = parse_blocks( $post->post_content );
    foreach( $blocks as $block ) {
    $block_name = $block[‘blockName’] )
    $block_content[$block_name] = render_block( $block );
    }
    }

    I wonder, you have a lot of experience in this maybe I approach the whole question from the wrong way altogether, but at least it would be great to be able to create function executing blocks if nothing else. Hope it helps others as well to have a better understanding of this.

    Then I could do
    echo $block_content[‘blockName’]
    But this process looks a bit cumbersome compared to something like
    echo get_field(‘fieldName’);

    Hope I am not saying something unreasonable, but it would be great to use these blocks more like custom fields

  5. Antal says

    Sorry in my last comment some paragraphs got mixed up, here is the right one:

    Hello Bill! Earlier you’ve mentioned that the ad blocks are custom blocks that execute a custom function. It seems to me a great idea, to have custom blocks inserted into the content that could be used to have dynamic content.
    So if I want to insert something after the 3rd paragraph I wouldn’t have to use the content filter, instead having a block sit there, which pulls the data from somewhere: for example from a meta field or from a custom post type post called for example “After 3rd paragraph”. And if the meta/linked post type has content it would be inserted and if not it just stays empty.

    For example, how could I link a php function to be executed in a custom block (without ACF pro)?:

    (here post 123 is a post that only serves to contain something to be displayed after the 3rd paragprah of each post that has the 3rd paragraph block)

    function custom_block_after_third_paragraph_function() {
    $content_for_third_paragraph_to_display = get_post(123);
    $content = $content_for_third_paragraph_to_display->post_content;
    if($content != “”){
    echo $content;
    }
    }

    Then I could create and include in the template some blocks that could be helpful to avoid filtering the content each time I would like to insert some temporary content after certain paragraph but I couldn’t wrap my head around how to create these php function executing blocks. And it would also open the possibility to create dynamic blocks, which sounds very useful.

    Also is there a way to get the content of certain blocks by name or ID to be echoed like a custom field?
    So for example instead:
    echo get_field(‘field name’);
    Would it be possible to do something like:
    echo get_block(‘block name’)?

    I’ve found some solution in your other article (https://www.billerickson.net/access-gutenberg-block-data/), but parsing each block and then assigning them into a variable one by one like this:

    function be_display_post_blockquote() {
    global $post;
    $blocks = parse_blocks( $post->post_content );
    foreach( $blocks as $block ) {
    $block_name = $block[‘blockName’] )
    $block_content[$block_name] = render_block( $block );
    }
    }

    Then I could do
    echo $block_content[‘blockName’]
    But this process looks a bit cumbersome compared to something like
    echo get_field(‘fieldName’);

    Hope I am not saying something unreasonable, but it would be great to use these blocks more like custom fields.

    I wonder, you have a lot of experience in this maybe I approach the whole question from the wrong way altogether, but at least it would be great to be able to create function executing blocks if nothing else. Hope it helps others as well to have a better understanding of this.

    • Bill Erickson says

      > How could I link a php function to be executed in a custom block (without ACF pro)?
      If you want a custom block, you’ll either need to use a block building plugin like ACF pro, or custom build the block with React. You cannot create custom blocks with PHP in WordPress. That’s the specific problem ACF Pro is solving.

      Also, your custom block would need to be present in the post. If you had a “third paragraph extras” block, it wouldn’t run in posts created without that block, or new posts that removed that block. It also wouldn’t predictably be in the third paragraph position because content editors could move it up and down.

      You might be better off using PHP to insert your custom code after X paragraphs. Here’s how I do it with support for both the block editor and classic editor: Insert after paragraph

      > Also is there a way to get the content of certain blocks by name or ID to be echoed like a custom field?

      Rather than building an array of all the blocks, I would create a helper function that loops through it and returns just the block you want. Ex: https://gist.github.com/billerickson/773d92e2ba2a50badaed5d543d31bc21

      • Antal says

        Hello Bill! Thank you very much, very insightful and thanks for the code snippets. It is interesting to see the usage of filtering the block display and the content combined for classic editor.

        I am a little bit confused by this, I have never seen this in a content filter, what are you preventing with this?
        // Only run on the main post
        global $wp_query;
        if( 0 !== $wp_query->current_post )
        return $content;

        Just one small thing I have noticed that in the classic editor version the after paragraph is dynamic while in the block editor version is hard coded 2.

        I am trying to avoid using the filtering the content. I am not sure about the depth of performance implications of that, but I was even thinking creating some plugin that would update the post content with all the filters applied – kind of like caching. But maybe page caching plugins such as WP Super Cache is helping with reducing PHP execution by doing the same, I am not so sure what these caching plugins are doing under the hood exactly.

        

I think if we need ACF Pro to be able to create blocks and if it is otherwise way too complicated it would prevent WordPress from having too many solutions based on this.

        I can follow and understand how the blocks are created, enqueued and defined I often use JavaScript in my solutions and have an understanding of how React works as well.
        

But in my opinion is way too complicated and I don’t understand why dynamic blocks are discouraged. It is very limiting if you can’t combine easily blocks, php code and custom fields seamlessly.

        Also I feel there is a bit of juggling between front-end display, back-end display and saving the data that you have to keep doing.

        I think at this point investing more time to become professional in how to juggle together a workable and reusable system of blocks that could be used in block templates might be not so beneficial. If there are only a handful of developers who need a pHD in curly braces to make blocks it won’t be too long until it has to be changed or if some (free) solution emerges as a winner for creating dynamic blocks which can use custom fields similar to ACF.

        Thanks for your help, insight and code Bill! Super cool!

        • Bill Erickson says

          While the_content filter is most commonly used for the post content, it can often be used elsewhere in the page. Sometimes a plugin or theme will do echo apply_filters( 'the_content', $my_custom_output ); to ensure it’s formatted properly (paragraphs added with wpautop(), oembed url’s converted to embed code, shortcodes processed…).

          $wp_query->current_post returns -1 when outside the loop, and 0 when the main post is displayed on singular content (or a higher number on archive pages indicating the current post in the loop).

          So that check ensures the following code only runs on the main loop, not any other custom code that is using the_content filter.

          Correct, the ea_insert_after_paragraph() function is a general purpose, utility function. When I use it on the_content filter I’m hardcoding what paragraph I want to insert after. I haven’t created a general purpose, utility function for the block editor one but could if I wanted.

          You might take a look at the register_block_type() function, which lets you register a block with PHP and run a callback function. This only works if you have no fields in the editor they need to customize.

          I agree, it is too difficult to create blocks the native WP way, but that’s true of a lot of core WP features. Creating custom metaboxes (and sanitizing the data) is a hassle, which is why plugins and code libraries like ACF and CMB2 were created in the first place.

          WordPress core tries to lay the groundwork for a feature and let plugins have their own take on how to implement it. It’s not my favorite approach, but it does ensure creativity and multiple solutions.

  6. Antal says

    Hello Bill! Thank you for your insightful reply.

    I believe the best way to use the blocks is how you described it in your “Building a block with Advanced Custom Fields” article. When you click on a block and you could add meta data on the sidebar and then you have a template file where you could use this data is perfect. Too bad it only works with ACF Pro.

    Still one of my concerns with querying blocks is that if I am interested in on block I should query them all and then run a foreach loop on them to find the one I would like to use. This just doesn’t seem intuitively something efficient or good practice.

    The other problem is that you have different save and edit functions that you have to write code twice like discussed here: https://github.com/WordPress/gutenberg/issues/14446

    Honestly I fail to understand what is the exact benefit of React inside WordPress. Managing the state would be too complicated combining PHP with React, loading small components on the front-end would seem to be overkill, because you need a whole library for that.

    If the goal was to make the editing experience more Reactive that sounds good, but the above mentioned issue of disconnect between save and edit seems to make it more cumbersome than beneficial. Maybe I am missing some bigger picture but it seems a bit Frankeinstenish for me. Maybe if there would be a large library of custom blocks to do all kind of stuff in an efficient way, that would be great.

    • Bill Erickson says

      Here’s a helper function I wrote for retrieving a specific block from a post (ex: echo be_get_block( 'core/quote' ); ).

      When building a native React block, yes, you create separate save and edit functions. When creating an ACF block you only create the frontend output of the block (the “save” function). Depending on your display settings, the “edit” view will either be an auto-refreshing version of the save function (ie: if you have it set to “preview” and manage the ACF settings in the side panel) or the edit view will be the ACF fields itself (ie: if you have it set to “auto”).

      The idea of the the separate save and edit functions is to provide a cleaner, more intuitive editing experience. Compare the core block editing experience to an ACF block using the “auto” mode (when you select the block, it disappears and becomes metabox-like block with input fields). The core fields feel more natural, but it does require a lot of extra work to build your own editing method for each custom block.

Leave A Reply