Skip to main content

As a WordPress developer with a focus on enterprise-level solutions, the idea of a multi-block plugin makes much more sense than creating individual plugins for each block. For large-scale systems, a well-designed architecture could easily involve dozens of custom blocks, along with additional features for modifying blocks or enhancing the editor experience.

While the concept of a multi-block plugin has been discussed online, there isn't a detailed, in-depth tutorial available. In this article, I will guide you through the process of creating a basic multi-block plugin and progressively adding advanced functionality to transform it into a robust solution that can be extended well beyond simple block registration.

By the end of this guide, you'll be equipped to create and manage your own multi-block plugin, supporting multiple blocks within a single plugin. You’ll also learn how to integrate advanced block editor features, such as variations, block styles, slot fills, and other essential tools to extend and customize your block functionality.

Benefits of a Multi-Block Plugin

  • Reduced Maintenance & Simplified Dependency Management: By consolidating multiple blocks into a single plugin, you can manage dependencies more effectively, reducing the need to update and maintain several separate plugins.
  • Shared Code & Reduced Duplication: A multi-block plugin enables you to share code across blocks, reducing redundancy and promoting more efficient development.
  • Improved Security Response: If a vulnerability is discovered in a shared dependency or component, it’s easier to address and contain the issue in one plugin rather than across multiple plugins. While this isn't a foolproof solution, it streamlines the security update process.

If you're new to creating custom blocks or the setup required for block development, I recommend first reviewing the process for setting up a Block Development Environment in WordPress.

For those following along with this tutorial, an example plugin is available for reference in this public repository.

Basic Setup

By the end of this section, you'll have created a single plugin called WP Multi Block. This plugin will register two static blocks and one dynamic block. Let's begin the process of building your block plugin.

Creating the Block Plugin

The first step is setting up a base plugin for your blocks. We’ll use the official create-block tool, which simplifies plugin scaffolding. Run the following command inside your /plugins folder to set up the initial plugin structure:

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

By default, @wordpress/create-block generates a static block. If you’d like to start with a dynamic block, you can use the --variant dynamic argument, which we'll cover later in this tutorial.

Static Block vs. Dynamic Block

  • Static Blocks: These blocks use a save() function to store the block’s HTML markup directly into the post_content table of the database. The content remains static once it's saved.
  • Dynamic Blocks: Instead of saving HTML, dynamic blocks use a PHP render callback to generate content on the server when the page is viewed. Only the block's attributes are saved in the database.

For this tutorial, we’ll stick with a static block for now.

Refactoring the Plugin Structure

To keep things organized as we add more blocks, let’s make a few adjustments to the plugin structure.

Create a Master Blocks Directory

  1. Inside the src directory, delete the existing files.
  2. Create a new folder called blocks within src.

Why add a blocks directory? By organizing blocks in this way, you make it easier to manage and scale your plugin. In the future, you may want to add src/scripts or src/styles, and having a dedicated blocks directory will keep everything organized as your plugin grows.

Create a Single Block

Now, let’s use the @wordpress/create-block tool to generate a basic block inside the src/blocks directory. Run the following command:

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

This will create a new block with the necessary files and setup in the src/blocks/block-one directory.

After running this command, your project structure should look something like this:

wp-multi-block/
│
├── src/
│   ├── blocks/
│   │   └── block-one/
│   │       ├── block.json
│   │       ├── edit.js
│   │       └── save.js
│   └── index.js
└── package.json

This structure will allow you to add more blocks easily later on. As you expand your plugin, you can follow this approach to add additional blocks while keeping everything centralized and well-organized.

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

Tags