Dynamic Blocks with Block.json, Vanilla JS, and No Build Process

WordPress blocks offer a dynamic experience in the post and site editors. But creating dynamic blocks – with contextual PHP-rendered functionality historically central to the WordPress platform – can be painfully difficult. The challenge goes far beyond learning a new API. Blocks leverage an entirely different toolset. And that toolset is optimized for a full-time professional developer profile, making even the simplest tasks highly complex.

The Need for Simple Dynamic Blocks

Why bother building dynamic editor blocks? Because they offer site owners significant flexibility thanks to the post and site editors. As these editors continue evolving, bringing existing theme and plugin features to the block environment unlocks myriad design and layout tools. This flexibility is unparalleled in previous WordPress interfaces. On the other hand, they should save the deep complexities of building native JS-forward blocks. Dynamic Blocks offer a great way to transition existing features into the future WordPress paradigm.

I worked my way through this process in my work to build version 2.0 of the Sheet Music Library plugin. The existing plugin has performed well for seven years. It works well with most themes out of the box. And it is used widely enough with existing content that it should not reasonably be rebuilt from scratch as a block-first project. But there are also limitations in its flexibility. Dynamic blocks, used in conjunction with custom block patterns and the new custom taxonomy term blocks in WordPress 6.1 allow sites to natively customize custom post type display. Post type displays are now customizable from individual query blocks within posts and pages to custom post type template editing in the site editor. To unlock that functionality each custom template part for a custom post type display needs a corresponding dynamic block.

Unfortunately, the process of creating dynamic blocks is quite complicated. This post shares my findings. Since this is a side project seeking to add features for my own website, first and foremost, my goal was to find the simplest possible solution. That means rendering blocks through existing PHP functions, registering blocks once through the block.json file, and using the minimum required JavaScript with no build process or other command line tools.

Block File Structure

Each block requires its own directory with four files:

  • slug/block.json
  • slug/slug-block-script.asset.php
  • slug/slug-block-script.js
  • slug/slug-block-style.css

Of these, block.json is the most useful, containing all of the block-specific data. The WordPress core documentation for block.json is generally good. However, keep in mind that some of the properties may not work as documented for dynamic blocks. Fortunately, basic supports and custom attributes can be defined here and are potentially usable without custom scripting.

Screenshot of block.json. See WordPress core documentation.
Screenshot of an example block.json. See the WordPress core documentation for a complete copy-editable example.

The style CSS file is also self-explanatory, containing styles for elements in your block. In most cases, these are likely to be duplicating existing plugin CSS, split out at this granular block level for rendering within the block editor.

Block Script and Asset Boilerplate

The code in this section is boilerplate intended to be copied into other projects and adapted. It is loosely, sort of documented, but fragmented in different places. And typically assuming very different use cases from the combination of dynamic blocks and vanilla JS documented here.

The block script “.asset.php” file is a PHP file that defines dependencies for the particular block. This is not an intuitive way to register dependencies, and does not follow existing patterns from other WordPress APIs. Depending on your particular block, this file’s contents may need to look something like this:

<?php
return array(
	'dependencies' => array(
		'wp-blocks',
		'wp-element',
		'wp-editor',
		'wp-components',
		'wp-i18n',
		'wp-server-side-render'
	),
);

All of the above scripts are essentially boilerplate and have nothing to do with the specific block functionality. Without this file and these script references, your dynamic block may not work (even if its sole purpose is to render an existing PHP function).

The actual block editor script JS file can also be confusing. After all, your block is registered in PHP, rendered from PHP, and fully defined through a static JSON file. But you must still register a standard, boilerplate script alongside each block. The script looks something like this:

( function ( blocks, element, serverSideRender, blockEditor ) {
    var el = element.createElement,
        registerBlockType = blocks.registerBlockType,
        ServerSideRender = serverSideRender,
        useBlockProps = blockEditor.useBlockProps;
 
    registerBlockType( 'example-plugin/slug', {
 
        edit: function ( props ) {
            var blockProps = useBlockProps();
            return el(
                'div',
                blockProps,
                el( ServerSideRender, {
                    block: 'example-plugin/slug',
                    attributes: props.attributes,
                } )
            );
        },
    } );
} )(
    window.wp.blocks,
    window.wp.element,
    window.wp.serverSideRender,
    window.wp.blockEditor
);

Again, this is all boilerplate code. You only need to edit the block slug (in two places) for each block. You can, of course, take this several steps further to render natively in JS or add more complexity. By default this focuses on rendering the block from PHP and saving block attributes (but not adding UI for additional custom attributes).

The parameters being processed in this boilerplate edit function may help clarify why the dependencies in the .asset.php file are required. Even if you’re not doing anything custom with them, they’re required for the block to save its state and assets. Also note that it is completely unnecessary to use JSX markup, or React, or a build process of any sort.

Working with Blocks in PHP

Once your block directory is set up with the four required files, it can finally be registered in PHP. For experienced WordPress developers, this function running on the init action will look straightforward:

add_action( 'init', 'prefix_register_blocks' );
function prefix_register_blocks() {

	// Register xxx block.
	register_block_type( plugin_dir_path( __FILE__ ) . 'blocks/slug/block.json', array(
		'render_callback' => 'php_render_callback_function_name',
	) );
}

This is where you define the render_callback, or the PHP function that displays your block content in both the editor and the frontend. This (most?) important piece of the puzzle conveniently stays within PHP. It can reference an existing function, perhaps that also displays shortcode or widget output. The referenced function takes the block’s attributes as a parameter and returns the block content markup:

// Returns markup for the xxx block or template part.
// $block_attributes is only provided when this is run as a block render_callback.
function php_render_callback_function_name( $block_attributes = null ) {

	if ( null === $block_attributes ) {
		$wrapper_attributes = 'class="wrapper-class"';
	} else {
		$wrapper_attributes = get_block_wrapper_attributes(['class' => 'wrapper-class']);
	}

	ob_start();
	?><div <?php echo $wrapper_attributes; ?>>
		<p>Block output html markup.</p>
	</div><?php

	$output = trim( ob_get_contents() );
	ob_end_clean();

	return $output;
}

To best adapt existing plugin functions to support block rendering, include the above $wrapper_attributes handling at the beginning of your function. Despite the documentation, get_block_wrapper_attributes throws a PHP warning when run outside of a block render callback. For best results, avoid calling that if there are no block attributes.

Adding the wrapper attributes to the output container brings in most of the block supports that work out of the box. You can optionally add additional handling for other block attributes. These can work even without building editor UIs – you could specify the attributes through the block code editor or even include them predefined in block patterns. There are a lot of possibilities without digging any deeper into the inner workings of the block API.

Current State of WordPress Development with Dynamic Blocks

Dynamic blocks are a great tool to transition older projects into the new WordPress paradigm. You can register a block and map it directly to an existing PHP function for output in the editor and on the front end. For now, it’s not quite that simple. You’ll also need to create four files and include a lot of boilerplate code to get functional blocks.

WordPress core could streamline this process to encourage broader block adoption. The block editor script and asset dependencies, in particular, are irrelevant for most widget, shortcode, or template-part-to-block conversions. Blocks without custom options should work with a block definition (block.json) and a PHP registration with render callback. In the meantime, hopefully this post offers a helpful guide through the extra steps.