Storing Block Patterns in HTML Files for Nicer Code [Technical]

Update: May 3, 2022

Big news! Starting with WordPress 6.0, you probably don’t need this technique at all.

With the next major version of WordPress, you’ll be able to use PHP files in a /patterns/ theme directory to automatically register patterns and include dynamic values.

You should even be able to combine those files with get_template_part() if you want to create composite patterns like what I suggested.

Cheers to core WordPress for coming up with a solution for all my needs!

Block patterns are an awesome feature in WordPress that let editors quickly insert complex combinations of blocks—even with preset settings—into the editor. I recently talked about them at the WordPress Seattle Developer Meetup, and I want to expand on one small code thing I showed in that presentation.

What Are Patterns?

With patterns, editors can quickly create attractive pages by inserting pre-designed placeholder content to customize. Here’s the pattern inserter showing two patterns I was working on today.

Block Patterns inserter in the WordPress block editor showing two large block patterns with background image and content.

These are full-screen banners that include the page title, a large intro paragraph, and an optional button, all in front of a few branded images.

Registering a Block Pattern

At its simplest, the register_block_pattern function only needs a namespaced slug, a title for the pattern, and the HTML content of the block(s) in the pattern. It’s also usually a good idea to include the viewportWidth attribute so that WordPress generates an attractive and realistic preview in the Pattern selection sidebar.

(Quick aside: If you’re like me and use Firefox as your primary browser, be aware that there’s an infuriating bug that hides Block Patterns previews in Firefox, seemingly at random.)

Here’s an extremely simple example:

namespace MRW\Theme;
add_action( 'after_setup_theme', __NAMESPACE__ . '\register_my_block_patterns' );
function register_my_block_patterns() {
	register_block_pattern(
		'mrw/my-block-pattern',
		array(
			'title' => __( 'My First Block Pattern', 'mrw' ),
			'content' => '<!-- wp:paragraph --><p>A single paragraph block style</p><!-- /wp:paragraph -->',
			'viewportWidth' => 1200,
		)
	);
}

That example is so extremely simple in fact, that it’s not particularly realistic.

Block Patterns become increasingly useful as they get more complex (and would therefore require more work to recreate). However, that can make the 'content' property of the settings array start to look really… interesting.

Here’s a more realistic example:

namespace MRW\Theme;
add_action( 'after_setup_theme', __NAMESPACE__ . '\register_my_block_patterns' );
function register_my_block_patterns() {
	register_block_pattern(
		'mrw/banner',
		array(
			'title' => __( 'Banner', 'mrw' ),
			'content' => '<!-- wp:cover {"url":"https://mysite.local/wp-content/themes/my-theme/img/block-pattern-images/bubbles-cover-background.png","id":86,"hasParallax":true,"dimRatio":0,"minHeight":100,"minHeightUnit":"vh","contentPosition":"bottom left","align":"full","className":"cover-header"} --><div class="wp-block-cover alignfull has-parallax has-custom-content-position is-position-bottom-left cover-header" style="background-image:url(https://mysite.local/wp-content/themes/my-theme/img/block-pattern-images/bubbles-cover-background.png);min-height:100vh"><div class="wp-block-cover__inner-container"><!-- wp:post-title {"level":1,"align":"wide","textColor":"black","className":"is-style-brush-font"} /--><!-- wp:paragraph {"fontSize":"large"} --><p class="has-large-font-size">Summary of the page. Make it awesome and inspirational!</p><!-- /wp:paragraph --><!-- wp:buttons --><div class="wp-block-buttons"><!-- wp:button --><div class="wp-block-button"><a class="wp-block-button__link">Optional Call to Action Button</a></div><!-- /wp:button --></div><!-- /wp:buttons --></div></div><!-- /wp:cover -->',
			'viewportWidth' => 1200,
		)
	);
}

I do not like how that looks.

Storing Block Patterns in HTML Files

After getting sick of ugly code like that, I realized that I could store the HTML in a separate file. I added a block-patterns folder to my theme, put a my-block-pattern.html file with the pattern’s HTML in the folder, and then grabbed the HTML from the file with file_get_contents:

namespace MRW\Theme;
add_action( 'after_setup_theme', __NAMESPACE__ . '\register_my_block_patterns' );
function register_my_block_patterns() {
	register_block_pattern(
		'mrw/my-block-pattern',
		array(
			'title' => __( 'My First Block Pattern', 'mrw' ),
			'content' => file_get_contents( get_theme_file_path( 'block-patterns/my-block-pattern.html' ) ),
			'viewportWidth' => 1200,
		)
	);
}

Dealing with Images / Dynamic File Paths

Using this technique brought calm and tidiness to my code editor but left me with one problem: the HTML is static. Why is that a problem? Look closely at the ugly block pattern code above and you’ll see it contains images that live in the theme’s folder.

Since I develop sites locally as most developers do, I need my block patterns to work regardless of where the site lives. It’s not uncommon for me to build a site locally (https://myclient.local), stage the site on my hosting (https://myclient.mrwweb.com), and then launch the site on the client’s domain. In the past I had just sucked it up and manually updated my block patterns containing file paths, but today I realized there was an easy solution staring me in the face:

  1. Define a “token” placeholder for the site path
  2. Do a find and replace for that string in my PHP code
namespace MRW\Theme;
function replace_theme_uri( $markup ) {
	return str_replace( '{{theme_uri}}', get_stylesheet_directory_uri(), $markup );
}
add_action( 'after_setup_theme', __NAMESPACE__ . '\register_my_block_patterns' );
function register_my_block_patterns() {
	register_block_pattern(
		'mrw/my-block-pattern',
		array(
			'title' => __( 'My First Block Pattern', 'mrw' ),
			'content' => replace_theme_uri( file_get_contents( get_theme_file_path( 'block-patterns/my-block-patterns.html' ) ) ),
			'viewportWidth' => 1200,
		)
	);
}

I chose {{theme_uri}} as my placeholder token. Then I used the replace_theme_uri function to replace it with the path to the theme folder! 🎉

With that problem solved, I see no other downsides to this approach, and it opens up other interesting possibilities!

Update (20 Apr 2022): More Dynamic Tokens

I’ve since used this approach on other sites and found myself needing to replace link URLs or media library URLs in addition to theme folder URLs. So now I’m taking the following approach that handles multiple potential placeholder tokens:

namespace MRW\Theme;
function replace_uris( $markup ) {
	$markup = str_replace( '{{uploads_uri}}', wp_get_upload_dir()['baseurl'], $markup );
	$markup = str_replace( '{{site_uri}}', site_url(), $markup );
	$markup = str_replace( '{{theme_uri}}', get_stylesheet_directory_uri(), $markup );
	return $markup;
}

Bonus: Composite Patterns

One of the possibilities I’ve used in a real project already is reusing pattern code multiple times in different combinations.

For instance, imagine your site has a “Banner” pattern, a “Three-column Feature” pattern, and a “Gallery with Description” pattern. Once those are in separate files, they can be mixed-and-matched to create patterns that represent full-page layouts:

namespace MRW\Theme;
add_action( 'after_setup_theme', __NAMESPACE__ . '\register_my_block_patterns' );
function register_my_block_patterns() {
	register_block_pattern(
		'mrw/banner',
		array(
			'title' => __( 'Banner', 'mrw' ),
			'content' => file_get_contents( get_theme_file_path( 'block-patterns/banner.html' ) ),
			'viewportWidth' => 1200,
		)
	);
	register_block_pattern(
		'mrw/banner',
		array(
			'title' => __( 'Three-Column Feature', 'mrw' ),
			'content' => file_get_contents( get_theme_file_path( 'block-patterns/three-column-feature.html' ) ),
			'viewportWidth' => 1200,
		)
	);
	register_block_pattern(
		'mrw/banner',
		array(
			'title' => __( 'Gallery with Description', 'mrw' ),
			'content' => file_get_contents( get_theme_file_path( 'block-patterns/gallery-with-description.html' ) ),
			'viewportWidth' => 1200,
		)
	);
	register_block_pattern(
		'mrw/banner',
		array(
			'title' => __( 'Landing Page', 'mrw' ),
			'content' => file_get_contents( get_theme_file_path( 'block-patterns/banner.html' ) ) . file_get_contents( get_theme_file_path( 'block-patterns/three-column-feature.html' ) ) . file_get_contents( get_theme_file_path( 'block-patterns/gallery-with-description.html' ) ),
			'viewportWidth' => 1200,
		)
	);
}

That code registers four patterns but only references three HTML files! The fourth pattern, “Landing Page”, is a composite block pattern only using code from other patterns. Update an individual pattern and the composite pattern will get updated too!


I hope this saves a developer or two some time and headaches. Let me know if you’ve found ways to improve on this in any way!

3 thoughts on “Storing Block Patterns in HTML Files for Nicer Code [Technical]”

  1. Excellent tips. I found this after trying to use get_template_part() to include the html and it dawning on me that it’s built for echoing.
    Splitting them up into parts and including multiple will make my life a lot easier!

    1. So glad you found this post useful, Rob! I was just using this technique yesterday, and I added the updated function that I’m using to replace two new “tokens” for the Uploads directory and site URL.

  2. I’m mostly using acf blocks, so it’s a case of replacing attachment id’s to match images and videos on the different sites.

    Every other post I read about patterns was saying that you had to escape double quotes… all because they were were pasting everything between double quotes.
    I knew their had to be a better way than that lol.

Join the Discussion

This site uses Akismet to reduce spam. Learn how your comment data is processed.