Creating portable ACF Blocks

For the last couple of months in my day job, I’ve been working some significant changes to Advanced Custom Fields block editor integration system, ACF Blocks.

ACF Blocks was first introduced to support WordPress 5.0’s initial implementation of the block editor back in ACF PRO 5.8, and as you can imagine that looked and worked very differently to the current way of working with blocks in WordPress 6.0.

Due to that legacy of needing to support so much backwards compatible code, ACF Blocks could never really keep up with the pace of development in the block editor. For this reason, the upcoming release of ACF 6.0 introduces block version. This allows users opt in to new versions of ACF Blocks, which can track new version of WordPress to let us implement all the new cool stuff.

I’m not going to go into detail here on all the changes coming, as we’ve got a public preview of ACF 6.0’s block changes over on GitHub — Go check that out read all the changes there.

Anyway, over on that thread this week, Bill Erickson asked if ACF could introduce a way to automatically load fields for a block from the block folder, meaning you’d have truly portable blocks.

One of my favourite things about the ACF Blocks rewrite in 6.0 is that ACF Blocks is now much closer to WordPress, in that we’re using WordPress’s own filters and hooks to extend in ACF Blocks. This means you get full support for everything WordPress introduces that doesn’t require block specific JS implementation, and because we’re using those filters and actions – you get the chance to add your own handlers along the way to extend things.

Specifically, this means because blocks can be registered earlier than acf/init, you can do things along the way, such as adding a handler to check every ACF registered block to see if the block folder also contains an acf-fields.json file – and ask ACF to register to those fields before acf/init, so they’re available to the block.

Here’s an example of how to do that:

add_action( 'acf/include_fields', 'add_block_fields' );
function add_block_fields() {
	// Loop over each ACF block.
	foreach ( acf_get_block_types() as $block ) {
		if ( isset( $block['path'] ) ) {
			$path = $block['path'] . '/acf-fields.json';
			if ( file_exists( $path ) && ! empty( $block['name'] ) ) {
				// Need to load this block.
				$json = json_decode( file_get_contents( $block['path'] . '/acf-fields.json' ), true );

				// Make sure the location for this field group is set to this block.
				$json['location'] = array(
					array(
						array(
							'param'    => 'block',
							'operator' => '==',
							'value'    => $block['name'],
						),
					),
				);
				$json['local']       = 'json';
				$json['local_file']  = $path;
				acf_add_local_field_group( $json );
			}
		}
	}
}

In my example here, we’re using a slightly customised version of an ACF JSON export that strips away the array at the top level, to just contain one field group, and because we know all the information about the block inside the load, we can dynamically inject the ACF Field Group location rules to ensure it’s displayed on the block.

If you want to see a fully working example of this, in a working “ACF Block as a plugin”, I’ve thrown up a second blog post today that walks you through that: Creating a portable “Slider” ACF Block