Currently reading: The Runaway Jury by John Grisham

Include Gutenberg patterns in your theme templates

Patterns are pretty nice when building a site for a client since they can be reused and inserted easily. If they're not the synced sort, ones that are updated everywhere at once, they can even be inserted and then edited for the needs of a given page or post.

But what if you wanted to go one step further and include one in your theme's templates? Maybe you've got a nice "contact us" CTA pattern and the client got tired of including it at the bottom of every page themselves. "Can you add a checkbox option for including it at the bottom automatically?" they ask, so you go about that business.

We'll need two helper functions: one to fetch a pattern from WP and another to render it. Up first is the fetcher. It both fetches and renders, thus requiring the renderer function to work, but I'm putting it first because it flows better in written format!

This function isn't much different than a normal post-fetching routine because Gutenberg patterns are posts.

/**
 * Fetch and render a pattern by its ID or slug.
 */
function get_pattern(int|string $id) : string {
	$args = [
		'numberposts' => 1,
		'post_type'   => 'wp_block'
	];

	$args    = array_merge($args, (is_int($id)) ? ['p' => $id] : ['name' => $id]);
	$pattern = get_posts($args);

	return ($pattern) ? render_post($pattern[0]) : '';
}

Note how the patterns are saved as a wp_block post type and that our function accepts both an ID and a slug ⸺ i.e. a post "name" in WP parlance. If you only wanted to accept an ID, you could rewrite this to use get_post(), the singular version of get_posts(), instead.

However you decide to go in the end, we'll have fetched the raw post content of the pattern in Gutenberg markup ⸺ which in many cases is simply an extension of normal HTML markup. Here's a basic snippet in case you've forgotten what that looks like:

<!-- wp:heading {"textAlign":"center","style":{"elements":{"link":{"color":{"text":"var:preset|color|white"}}}},"textColor":"white","fontSize":"large"} -->
<h2 class="wp-block-heading has-text-align-center has-white-color has-text-color has-link-color has-large-font-size">Contact Us</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut elit tellus, luctus nec ullamcorper mattis, pulvinar dapibus leo.</p>
<!-- /wp:paragraph -->

You can recognize it by its HTML-style comments that start with <!-- wp:. Now it's time to render that mess ⸺ to turn Gutenberg markup into HTML for display in the browser, and so enters our renderer function.

/**
 * Given a post ID, post object, or string of Gutenberg markup, render its content.
 */
function render_post(int|string|WP_Post $post) : string {
	$content = (is_string($post)) ? $post : get_post_field('post_content', $post);

	if (! $content) return '';

	$content = parse_blocks($content);
	$content = array_map('render_block', $content);
	$content = array_map('trim', $content);
	$content = array_filter($content);
	$content = implode("\n", $content);

	return wptexturize($content);
}

The parse_blocks() and render_block() functions from WP do the heavy lifting for us as they are the foundation of Gutenberg. The former gives us an array of block objects while the latter takes one of those block objects and does the conversion to HTML. array_map() allows us to hit them all in one succinct line.

We then trim away any excess white space, remove any empty array elements that may have cropped up, implode the array down a string, and finally texturize that string. It's quite simple in the end, and if needed you can run your own custom filters at any step of the way.

And that's all there is to it. Your templates are now capable of rendering any arbitrary patterns you've created. All you need to know is the ID or slug.

<?= get_pattern(42) ?>
<?= get_pattern('contact-us') ?>

As always, these functions are mere examples. Your needs and setup may vary, so it may make sense for you to have them stashed away in some helper class under a namespace. Going for such generic and short-named functions in the global space can risk collisions if you don't have, or won't have in the future, full control over the install and its configuration.

Leave a comment

Your email address will not be published. Required fields are marked *