Skip to content

Michael LaRoy - Home

WordPress ACF Blocks with block.json and InnerBlocks


In a previous post, on using ACF with WordPress Blocks, I detailed how I created custom blocks in the Sage theme using a couple of approaches. This time, I want to tell you about my approach for using ACF blocks using the block.json way. This is partially inspired by chats with my friend Joey who wrote about registering blocks in a similar way, but takes it in a bit of a different direction.

What is block.json about?

According to the official WordPress developer resources, they “…encourage using the block.json metadata file as the canonical way to register block types.” Using the PHP register_block_type function in your WP theme or plugin, you can reference the path to this json file to register your block.

In my previous post (linked above), we discussed using the ACF function acf_register_block_type to do this. However, even though we are using ACF still, we can set this method aside and go with the one that WP provides out of the box:

register_block_type( dirname(__FILE__) . '/block.json' );

It’s THAT easy. Almost. Let’s dig in and see what else we need to do. Let’s start with some files and folders to build a Hero block. In my theme, I have a blocks directory, which contains a sub-folder for each block that I will create:

/theme
└─── functions.php
└─── /blocks
│   └─── /hero
│       │   block.json
│       │   index.php

In our hero block’s block.json, we should start with this json snippet:

{
	"name": "custom-hero",
	"title": "Hero",
	"description": "Displays ACF Hero block.",
	"category": "theme",
	"apiVersion": 2,
	"acf": {
		"mode": "preview",
		"renderTemplate": "blocks/hero/index.php"
	},
	"supports": {
		"anchor": true,
		"jsx": true
	}
}

Notice the acf key here:

    ...

    "acf": {
        "mode": "preview",
        "renderTemplate": "blocks/hero/index.php"
    },

This gives a bit of information to WordPress and to ACF about the block, and how to display it in the block editor.

First, it indicates the default display mode. If you have registered an ACF field group and assigned it to this block, the “mode” here will show the actual rendered block; otherwise it will show the fields for the block.

Second, the “renderTemplate” is the path to the file that contains your template markup code for this block, relative to your functions.php file, usually the root of your theme.

Registering my custom blocks

In my functions.php file, I will register my blocks with this file structure in mind.

// get an array of all of the block.json files in my blocks directory
$block_json_files = glob( get_template_directory() . '/blocks/**/block.json' );

// auto register all blocks that were found.
foreach ( $block_json_files as $block_json_file ) {
    register_block_type( $block_json_file );
};

Now, all I need to do to add a new block is to create a new sub-folder under /blocks, and make sure both a block.json and an index.php.

Using InnerBlocks

If I want to leverage the WordPress core blocks inside my custom Hero block, I can totally do that. If you notice this part of the block.json, this is what enables the use of InnerBlocks right inside our custom ACF block. Specifically, the JSX key in the supports object:

    ...

    "supports": {
        "anchor": true,
        "jsx": true
    },

In your index.php block template, you can add the JSX snippet to pull in the InnerBlocks in the block. You can define both which blocks are allowed, and which blocks to insert by default:

<?php
$default_blocks = array(
    array('core/heading', array(
		'level' => 1,
		'placeholder' => 'This is a placeholder heading',
        'className'   => 'hero-heading'
	))
);

$allowed_blocks = ['core/heading', 'core/paragraph'];
?>

<div class="hero-block">
    <InnerBlocks
        template="<?php echo esc_attr( wp_json_encode( $default_blocks ) ); ?>"
        allowedBlocks="<?php echo esc_attr( wp_json_encode( $allowed_blocks ) ); ?>"
        className="hero-inner-blocks"
    />
</div>

Using InnerBlocks in this way, I can style my custom block exactly the way I want in my CSS, leverage core blocks, and skip adding an ACF field group altogether if I want to. In this case, I get an h1 heading block right away, and I can also add a paragraph block if I want to. The allowedBlocks limits me to these options - this can be a great mechanism to prevent all sorts of nonsense a rogue content editor might employ…

no nonsense from the Office

Notice the use of className in both the array of default blocks, as well as in the <InnerBlocks /> chunk itself. This is due to the “JSX” factor - under the hood, blocks are really React components, and JSX is the templating mechanism for that which requires this syntax.

If your use case calls for something a bit more complex, then you’d want to combine this method with the traditional ACF field group, and get your fields in the way you’re already familiar with:

<?php $some_data = get_field('hero_data'); ?>

<div class="hero-block">
    <InnerBlocks
        template="<?php echo esc_attr( wp_json_encode( $default_blocks ) ); ?>"
        allowedBlocks="<?php echo esc_attr( wp_json_encode( $allowed_blocks ) ); ?>"
        className="hero-inner-blocks"
    />
    <div>
        <?php echo $some_data; ?>
    </div>
</div>

Granted, this is a pretty simplistic use case, but you can certainly combine core/inner blocks with ACF fields to set a background image, or whatever else you want in there.

Bonus: block styles

An easy way to add a different style to your block is by a adding little bit more to your block.json. Just like the default core/button block comes with styles like “rounded,” “fill,” and “outline,” you can add styles to your block:

    ...

    "styles": [
        {
            "name": "large-hero",
            "label": "Large Hero",
            "isDefault": false
        },
        {
            "name": "mini-hero",
            "label": "Mini Hero",
            "isDefault": false
        }
    ],

Now you can select which “style” of hero you want right in the block editor, just by clicking a button. All this does is add a class like “is-style-large-hero” to the $block variable which ACF provides for free to your template.

Here’s how you might use that:

<?php
$classes = ['hero-block'];
if( !empty( $block['className'] ) )
    $classes = array_merge( $classes, explode( ' ', $block['className'] ) );
?>
<div class="<?php echo esc_attr( implode( ' ', $classes ) ); ?>">
    <InnerBlocks
        template="<?php echo esc_attr( wp_json_encode( $default_blocks ) ); ?>"
        allowedBlocks="<?php echo esc_attr( wp_json_encode( $allowed_blocks ) ); ?>"
        className="hero-inner-blocks"
    />
</div>

hero block in the WordPress block editor

Block styles comes for free from WordPress directly, and has nothing to do with ACF. As I mentioned above, the $block variable is provided by ACF, along with the $is_preview and $post_id variables. You can use these to further enhance the capabilties and features of your block.

Checkout the WordPress references guide to learn about what other things you can do with your block via block.json.

Now, go have fun making blocks with ACF!

5 Accessibility Fixes You Can Make Today

Learn about the most common reasons that websites fail accessibility standards, and what you can do about it.