WordPress.org

WordPress Developer Blog

How to build a multi-block plugin

How to build a multi-block plugin

As a WordPress developer who has mainly focused on enterprise level solutions, the concept of a multi-block made far more sense than having several individual block plugins. Thinking long term, a well-designed enterprise system could easily have dozens of custom blocks and other functionality related to modifying blocks or improving the editor experience.

The concept of a multi-block plugin is something I’ve seen mentioned online a couple of times, but I don’t recall seeing an in-depth tutorial. In this article, I will create a basic multi-block plugin then add advanced functionality to turn it into a robust block plugin that can be extended beyond registering blocks.

By the end of this article you will have gone through the steps to set yourself up with your own multi-block plugin and will be ready to build out and manage any number of blocks in one centralized plugin, along with other block editor related functionality such as variations, registering block styles, adding slot fills, and anything else you may need.

Benefits to a Multi-Block Plugin

  • Less maintenance, easier dependency management
  • Increase shared code, reduce duplication across plugins
  • Potential improvements to security response time (I’m no expert, but a hole in a key dependency is easier to address and contain in 1 plugin than 10)

If you are unfamiliar with creating custom blocks and the system setup, requirements to do so, please read up on creating a Block Development Environment in WordPress.

If you are building along with this tutorial there is an example of the plugin for reference that can be see in this public repo.

Basic Setup

By the end of this section of the tutorial you will have built a single plugin called WP Multi Block that will register two static blocks and a dynamic one. Let’s get started by creating your block plugin.

Creating a Block Plugin

The first step is to get a base plugin setup for blocks. For this, you will use the officially supported `create-block` tool. This tool will take care of scaffolding this plugin, all by running one simple command inside your /plugins folder:

npx @wordpress/create-block@latest wp-multi-block

By default, the @wordpress/create-block generates a static block, but there’s an argument --variant dynamic that can be used if you wish to start with only a dynamic block. If you’re following along you’ll use this argument later in this tutorial.

What’s the difference between a static block and a dynamic block? At a very high level, static blocks use a save() function to store HTML markup for the block into the post_content table in our database. Dynamic blocks use a PHP render callback to render content on the server side when the page or post is viewed, only storing the block’s attributes in the database. For more details you can read here on the Developer Blog Static vs. dynamic blocks: What’s the difference?

For the purposes of this tutorial you will be using the basic command and starting with a static block.

Refactor the Plugin Structure

To better organize multiple blocks in the plugin there are some changes you  can make to the plugin structure.

Create a Master Blocks Directory

Inside the src directory do the following:

  • Delete all existing files
  • Create a folder called blocks

Why add a blocks directory, it seems unnecessary? We’ll want to be able to add a src/scripts and maybe a src/styles later, so grouping blocks will improve long-term organization and maintenance.

Create a Single Block

There’s a way we can use @wordpress/create-block to set up only what is required for a block by using the --no-plugin option. Inside of the src/blocks directory and run the following:

npx @wordpress/create-block@latest block-one --no-plugin

Here’s what my updated directory structure looks like in VS Code:

Update the Register Block Type Action

With the block moved to a new directory you now need to update the reference to its location in the register_block_type action:

  • Edit the wp-multi-block.php file
  • Let’s give the existing function a better name and use multiblock_register_static_blocks
  • In the register_block_type function you need to include the new path to our block so that it looks like /build/blocks/block-one

Our final code looks like this:

function multiblock_register_blocks() {
	register_block_type( __DIR__ . '/build/blocks/block-one' );
}
add_action( 'init', 'multiblock_register_blocks' );

Update the Build Files

  • It’s time to regenerate the build files by running npm run build

Let’s confirm that these changes work, activate the plugin and refresh the editor, and add our first block. We should end up with something that looks like this:

Adding additional blocks

Adding more blocks is as easy as running the same command as you did when creating the first block, but with a different name. Go ahead and create Block Two.

Create a new block

You can  repeat what you did to create the  first block by running the following inside the src/blocks directory:

npx @wordpress/create-block@latest block-two --no-plugin

Here’s what my updated directory structure looks like in VS Code:

Update Register Block Type Action

  • Edit the wp-multi-block.php file and duplicate the register_block_type function and change the reference to /build/blocks/block-two.

The final code looks like this:

function multiblock_register_blocks() {
	register_block_type( __DIR__ . '/build/blocks/block-one' );
	register_block_type( __DIR__ . '/build/blocks/block-two' );
}
add_action( 'init', 'multiblock_register_blocks' );

Update the Build Files

  • It’s time to regenerate our build files by running npm run build

Let’s confirm that these changes work and refresh the editor and add both of our blocks. We should end up with something that looks like this:

Adding a Dynamic Block

So far, you only worked with static blocks, but how does this apply to a dynamic block? With a few updates, you  can also include dynamic blocks along with our static blocks. If you aren’t familiar with both block types you can read here on the Developer Blog Static vs. dynamic blocks: What’s the difference?

You’ll start by creating a new block just like we did before, but this time we’ll add an option to specific that we are creating a dynamic block. Go ahead and create Block Three.

Create a Dynamic Block

You can once again reach for @wordpress/create-block to create our dynamic block. Inside the src/blocks directory and run the following:

npx @wordpress/create-block@latest block-three --no-plugin --variant dynamic

Update Register Block Type Action

  • Edit the wp-multi-block.php file and duplicate the register_block_type function and change the reference to /build/blocks/block-three.

The final code looks like this:

function multiblock_register_blocks() {
	register_block_type( __DIR__ . '/build/blocks/block-one' );
	register_block_type( __DIR__ . '/build/blocks/block-two' );
	register_block_type( __DIR__ . '/build/blocks/block-three' );
}
add_action( 'init', 'multiblock_register_blocks' );

Update Build and Start Commands

Before you can update your build you need to add an argument into the build commands. By adding --webpack-copy-php you are  telling the build process to include any PHP file into the final build directory.

  • Edit package.json
  • Update the build and start commands to tell webpack to copy the PHP files.

The final commands should look like the following:

"build": "wp-scripts build --webpack-copy-php"
"start": "wp-scripts start --webpack-copy-php"

Update the Build Files

It’s time to regenerate our build files by running npm run build

Let’s confirm that these changes work and refresh the editor and add all three of our blocks. We should end up with something that looks like this:

Note about text domain

When using @wordpress/create-block the text-domain in the block.json matches the block name. In your plugin, each block will have a unique text-domain. This would be a great time to edit each block.json file and change the text-domain to wp-multi-block.

Recap of Basic Setup

Congratulations! If you’ve followed along, you’ve just created a multi-block plugin that loads three blocks and is maintained with a single set of dependencies. And you can repeat the steps to add new blocks as many times as you wish. The key points to remember are:

  • Individual block names must be unique so don’t forget to update references to names and titles throughout the block files
  • Each block must be registered with a register_block_type function

Each dynamic block must have a reference to a PHP file as part of its register_block_type function  which is defined in the block.json file

Advanced Configuration

While each section from this point forward is completely optional as you already have a fully functional multi block, I would recommend going through these additional steps to improve the overall configuration of the plugin to better streamline development, simplify maintenance and improve the overall readability of the code.

By the end of this section of the tutorial you’ll have:

  • Added another dynamic block
  • Created an array of blocks
  • Updated functions to use a foreach
  • Added webpack to compile our assets into a single resource
  • Added a script to modify a core block style.

Adding a new register_block_type function is pretty easy, but what does that code look like repeated 20 times or more? Instead of that you can add an array of blocks, then use a foreach to register each block in the array.

Adding Another Dynamic Block

Before we dive in it will be helpful to have a second dynamic block for testing purposes, so create one inside the src/blocks directory:

npx @wordpress/create-block@latest block-four --no-plugin --variant dynamic

When done creating block-four you’ll need to build again by running npm run build. This new block won’t be available in the editor just yet, we have to update a few functions first.

Create an Array and Add foreach Loop

Start by creating an array that allows you to quickly and easily add new blocks to our plugin as needed without having to ever add another register_block_type function. Then use a foreach to loop through the array and register each block in the register_block_type function. Edit the wp-multi-block.php file and update the multiblock_register_blocks function to look like this:

function multiblock_register_blocks() {
	$custom_blocks = array (
		'block-one',
		'block-two',
		'block-three',
		'block-four',
	);
	
	foreach ( $custom_blocks as $block ) {
		register_block_type( __DIR__ . '/build/blocks/' . $block );
	}
}
add_action( 'init', 'multiblock_register_blocks' );

Confirm that these changes work by refreshing the editor or the frontend and adding all four of the blocks. We should end up with something that looks like this:

Now when you add new blocks all that is needed is a quick addition to the array of blocks, which keeps the rest of our code slim and much easier to maintain.

Combine Block Assets

By default, the loading of script files is handled in the block.json file. That works well for single block plugins, in a multi-block plugin you might consider streamline loading script files.

Now, there are some pros and cons to this approach, it might not make sense for everyone. On one hand, you reduce  the number of loaded items and you have more control over the output. You  also lose a nice feature like loading a view.js file only when the block is present on the page.

For the purpose of this tutorial, the preferred approach is to combine and compile our JavaScript and CSS files with the idea that you will gZip and host them on a content delivery network (CDN) in the future. To set this up you will add webpack, leverage the core WordPress default webpack config and package this all up into a single script and stylesheet.

Create a webpack Config File

First create a webpack config file. Here you will spread in the webpack config from the @wordpress/scripts package as you want to maintain that functionality while adding your own.

Start by creating a webpack.config.js file in the root folder  of your plugin and paste in the following:

const defaultConfig = require( '@wordpress/scripts/config/webpack.config' );
const path = require( 'path' );

module.exports = {
    ...defaultConfig,
    entry: {
        'multi-block-editor': [ path.resolve( __dirname, 'src/multi-block-editor.js' ) ],
        'multi-block-frontend': [ path.resolve( __dirname, 'src/multi-block-frontend.js' ) ],
    },
    output: {
        path: path.resolve( __dirname, 'build' ), filename: '[name].js',
    },
};

In the webpack file you’re spreading in the config file available as part of the @wordpress/scripts package and then add your own entries that are used to compile and load into the editor and the other to compile assets that will load on the frontend. We’ll give these entry points the names multi-block-editor and multi-block-frontend.

Add Editor and Frontend Entry Points

Now you need to create our entry point files that you specified in the webpack.config.js file.

First add the script for the block editor. In the src directory create a file called multi-block-editor.js, and paste in the following:

import './blocks/block-one';
import './blocks/block-two';
import './blocks/block-three';
import './blocks/block-four';

Next add the script for the frontend assets. In the src directory create a file called multi-block-frontend.js, and for now simply paste in the following:

console.log( 'frontend test' )

Enqueue the editor and frontend assets

Enqueue Editor Assets

Now that you have the script and style files being output as single files you need to enqueue them into the editor using the enqueue_block_editor_assets action.

Open up the wp-multi-block.php file and add the following function:

function multiblock_enqueue_block_assets() {
	wp_enqueue_script(
		'multi-block-editor-js',
		plugin_dir_url( __FILE__ ) . 'build/multi-block-editor.js',
		array('wp-blocks', 'wp-components', 'wp-data', 'wp-dom-ready', 'wp-edit-post', 'wp-element', 'wp-i18n', 'wp-plugins'),
		null,
		false
	);
	
	wp_enqueue_style(
		'multi-block-editor-css',
		plugin_dir_url( __FILE__ ) . 'build/multi-block-editor.css',
		array(),
		null
	);
}
add_action( 'enqueue_block_editor_assets', 'multiblock_enqueue_block_assets' );

Enqueue Frontend Assets

Now you need to enqueue the frontend assets by adding another function to the wp-multi-block.php file:

function multiblock_enqueue_frontend_assets() {
	wp_enqueue_style(
		'multi-block-frontend-css',
		plugin_dir_url( __FILE__ ) . 'build/style-multi-block-editor.css',
	);

	wp_enqueue_script(
		'multi-block-frontend-js',
		plugin_dir_url( __FILE__ ) . 'build/multi-block-frontend.js',
		array(),
		null,
		true
	);
}
add_action( 'wp_enqueue_scripts', 'multiblock_enqueue_frontend_assets' );

Updating Block Styles

In this configuration, each block has a separate file for editor and frontend styles. In most cases, what we are loading on the frontend is also what we want to load in the editor. What I prefer to do here is import my style.scss file into the editor.scss file. This way you can maintain one set of frontend classes while adding anything unique to the editor.

Here’s an example of how such an editor.scss file would look like for block-one after a couple of updates. The border is only added in the editor, and by adding .is-selected the border only appears when a block-one is selected.

Update each of the editor.scss files for each of our blocks to import the style.scss file like this example:

@import "./style.scss";

.wp-block-example-block-one.is-selected {
    border: 2px dotted #f00;
}

Removing Unused Files and Clean Up block.json

Now that you have single file assets in place we can go through our blocks and delete any view.js files not being used, these are no longer loaded via the block. If you are adding blocks to your own multi-block plugin and have blocks that require this functionality you can include those in our frontend entry point js file. To learn how the view.js file plays a role in using the Interactivity API, you can read A first look at the Interactivity API on this site. 

In each of the blocks, edit the block.json files to remove references to the js and css files. You are looking to remove the following items:

"editorScript": "file:./index.js",
"editorStyle": "file:./index.css",
"style": "file:./style-index.css",
"viewScript": "file:./view.js"

Note: in the dynamic blocks make sure you do not remove the render line that looks like this:

"render": "file:./render.php"

Go ahead and run npm run build to generate new plugin assets and go test the new block in the editor as well as the frontend of the development environment.

Additional Scripts

One final thing that you will include in your plugin is an example of how to add additional scripts to the compiled editor script. These scripts can be used for block modifications or variations, slot fills, or other functionality.

Add a simple script to remove the outline style on a core button.

Add a Script to Modify a Block Style

First you need to install a couple of new dependency:

npm install @wordpress/blocks @wordpress/dom-ready --save-dev

Once the package is installed create a script file like the following:

  • Create a scripts directory at src/scripts/modifications
  • Create a file named button.js and paste the following:
import { unregisterBlockStyle } from '@wordpress/blocks';
import domReady from '@wordpress/dom-ready';

domReady(() => {
    unregisterBlockStyle( 'core/button', array( 'outline' ) )
});

Now you need to include this script into the block editor entry point:

  • Edit multi-block-editor.js
  • Import the button.js so your revised file looks like:
// Import the plugin blocks
import './blocks/block-one';
import './blocks/block-two';
import './blocks/block-three';
import './blocks/block-four';

// Import other block scripts
import './scripts/modifications/button';

Now all you need to do is update the build files with npm run build and refresh the editor. Try adding a button you’ll see the outline style option is now gone.

If you are interested in more ways to curate the editor experience, the post 15 ways to curate the WordPress editing experience offers additional code snippets to streamline content creation, ensure consistency, and create personalized editing experiences.

Wrapping Up

Author’s note: I hope others out there find benefit in this approach to building a block plugin in some of their future work. I’m sure there are several ways that my approach can be approved upon, and I would love to hear how we can do that as a community.

For an tutorial with a different approach, see the article Setting up a multi-block plugin using InnerBlocks and post meta.

Resources

Documentation

Developer Hours

Courses on Learn.WordPress

Props @bph, @milana_cap and @flexseth for the review, feedback and additional resources.

3 responses to “How to build a multi-block plugin”

  1. Drivingralle Avatar

    Images right after
    “Here’s what my updated directory structure looks like in VS Code:”
    and
    “Let’s confirm that these changes work and refresh the editor and add both of our blocks. We should end up with something that looks like this:”
    are swapped. And after adding the second block too.

    1. Troy Chaplin Avatar

      Thank you for the heads up, I’ve updated the images that were mixed up.

  2. mrwweb Avatar

    Really great post! It’s awesome to have a canonical reference for this very common need (even on small sites!).

    FYI, “If you are building long with this tutorial”, should have “along” instead of “long” (missing “a”).

Leave a Reply

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