Rendering Dynamic Gutenberg Blocks in Theme Template Parts

Gutenberg is an ambitious project that aims to completely overhaul the experience of writing content in WordPress.

One of the problems you’ll soon run into when building a block for Gutenberg is that as a block becomes more complex, storing its complete output statically becomes undesirable. If a block contains several fields or its output contains HTML markup, you don’t want to find yourself in a situation where this output needs to be changed at some point in the future and you need to retrospectively apply changes to stored block output in every post.

Gutenberg supports dynamic block rendering so that you can perform more complex output rendering on the fly, without having to store the complete output when the block is saved. This is the same method that shortcodes in WordPress use and allows you to move away from static block output. If you’re building a block for Gutenberg that uses anything more than very simple output, you should consider using dynamic rendering.

You can take this one step further and build a dynamic block for Gutenberg which uses a theme template part for its output. This way, the theme on your site really is in control of its output, regardless of where the block is registered.

The code below introduces a function which accepts parameters for a block name, block attributes, and stored block content, and returns the contents of a theme template part that matches its name. By using a theme template part, you get support for parent and child themes, separation of data from the presentation, and increased portability. This function can be reused for any block that uses dynamic rendering (or you can use it to add dynamic rendering to an existing block).

Unfortunately, template parts in WordPress don’t support the passing in of variables to use within its scope, so the code below uses the query var method which is about as clean as you can get.

/**
 * Generic block rendering callback function to load a block from a theme template part.
 *
 * Loads a block from the `blocks` subdirectory according to the name of the block, and places the
 * block attributes and block content into namespaced query vars. If there's no corresponding block
 * template part, the block content is returned unaltered.
 *
 * A block named `foo/block1` looks for a template part named `blocks/foo/block1.php`.
 *
 * The block attributes and content can be accessed inside the template part via query vars:
 *
 * - `get_query_var( 'foo/block1/attribute1' )`
 * - `get_query_var( 'foo/block1/attribute2' )`
 * - `get_query_var( 'foo/block1/content' )`
 *
 * @param string $name       The full block name, for example 'foo/block1'.
 * @param array  $attributes Array of attributes saved on the block instance.
 * @param string $content    Optional user-entered block content. Can be null.
 * @return string The dynamic block content.
 */
function template_part_block_renderer( string $name, array $attributes, string $content = null ) : string {
	// Set query vars so they are accessible to the template part:
	foreach ( $attributes as $attribute_name => $attribute_value ) {
		set_query_var( $name . '/' . $attribute_name, $attribute_value );
	}
	set_query_var( $name . '/content', $content );

	// Load the template part in an output buffer:
	ob_start();
	get_template_part( 'blocks/' . $name );
	$output = ob_get_clean();

	// Fall back to just the block content if there's no template part:
	if ( '' === $output ) {
		$output = (string) $content;
	}

	// Clear the query vars so they don't bleed into subsequent instances of the same block type
	foreach ( $attributes as $attribute_name => $attribute_value ) {
		set_query_var( $name . '/' . $attribute_name, null );
	}
	set_query_var( $name . '/content', null );

	return $output;
}

$blocks = [
	'block1',
	'block2',
	'block3',
];

foreach ( $blocks as $block ) {
	register_block_type( "foo/{$block}", [
		// https://github.com/WordPress/gutenberg/issues/4671
		'render_callback' => function( array $attributes, string $content = null ) use ( $block ) {
			return template_part_block_renderer( "foo/{$block}", $attributes, $content );
		},
	] );
}

Let me know what you think!

6 replies on “Rendering Dynamic Gutenberg Blocks in Theme Template Parts”

  1. Hello John,

    Your code work fine and will be perfect for my futur projects.

    I just suggesting a new function to simply the template code. It return an array with the block’s attributes :

    
    function get_block_attributes( $block ) {
      global $wp_query;
      if( substr( $block , -1) != '/' ) $block .= '/';
    
      $attributes = array_filter($wp_query->query_vars, function ($k) use ($block) {
        return $k == strstr($k, $block);
      }, ARRAY_FILTER_USE_KEY);
    
      $return = [];
      foreach ($attributes as $key => $value) {
        $return[str_replace( $block, '', $key )] .= $value;
      }
      return $return;
    }
    
  2. Sadly, Gutenberg has stopped passing the block content to the render callback (only attributes are passed now.) An issue has been opened to restore that functionality, so please go give it a thumbs up if that’s something you’d like to see added back to Gutenberg. https://github.com/WordPress/gutenberg/issues/5760

  3. Thank you very much!
    I’m wondering if wordpress is will provide a cleaner way to use separate files to output dynamic blocks.

    Just found out about your article and have made a similar issue myself a few days ago:
    https://github.com/WordPress/gutenberg/issues/7331

    Hard to find reliable and up2date information for Gutenberg exploring more in-depth and real life examples.

  4. Current Gutenberg version 7.1 support 2 arguments on the server render callback, the first is $attributes and the second is $content, e.g. my_server_side_render_callback($attributes, $content){ \\output markup here… }
    You can still get the content out of the save function on the server side when using dynamic blocks, this is usually used when having an inner block content inside the dynamic block and it needs to be displayed as part of the final render.

Comments are closed.