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.

19 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”).

    1. Troy Chaplin Avatar

      Thanks, I’ve made the update

  3. Aileen Avatar
    Aileen

    This is really helpful, thank you. I have a question – what if I want to keep the styles and scripts for the blocks separate, as they were originally. But I also want to add and build some additional scripts, eg for block variations or customisations. Is that possible? What would I need in webpack.config.js to build my additional scripts but to keep the blocks separate as they were before?

    1. Troy Chaplin Avatar

      Thank you, glad you found it helpful. While I haven’t tested what you are looking to do, this came up as I was writing the article and I do want to cycle back and see what that could look like. I may take a look at this soon, if so I’ll let you know.

    2. Troy Chaplin Avatar

      Hey Aileen,

      I had a look at this and came up with a solution to build blocks individually while adding any other additional scripts that are needed. I have pushed a branch on the example repo that has a functional solution: https://github.com/wptrainingteam/building-multi-block-plugin/tree/DontCombineBlockScripts

      Key changes were:
      – Removed the webpack.config.js
      – Created uniquely named webpack.custom.js and set this up without importing anything from wordpress/scripts
      – Removed the imports of all the blocks from multi-block-editor.js

      Once I recreated my blocks so I had all the files previously deleted and ran my build everything worked as expected after some testing.

      1. Aileen Avatar
        Aileen

        This looks, great, I’ll definitely try it, just what I need for my plugin right now.
        Thank you!

      2. Aileen Avatar
        Aileen

        Does your new version still support adding additional scripts (eg variations or your Unregister Block Style example)? I don’t see that in your example, and I am having trouble getting that to work in the build.

        1. Troy Chaplin Avatar

          I took a look at you’re right, there’s still something that is not functioning properly and while the additional script is being built and enqueued, it is not functioning as expected. I have an idea why, but not clear on how to fix just yet.

          But I’m quite curious now as well so I’m gonna find some time to try and figure this one out. I will reply and let you know if I do find a solution.

        2. Troy Chaplin Avatar

          Hey Aileen,

          I’m happy to say that I put together a solution that will maintain the original output of the blocks when building while adding additional scripts for various block modifications and more.

          The main change here is in the webpack config file entry points where I am including the entry from the default config. This handles the block build separate from the other entries, where you can add as many as you want.

          I have pushed these changes to a branch that you can see here: https://github.com/wptrainingteam/building-multi-block-plugin/tree/DontCombineBlockScripts

          As a part of this update I also removed the original blocks and created one for a static block and one for a dynamic block, as well as a new admin javascript file. I also updated the structure in the src folder to better organize the script entry points and adding a helpers folder for various other scripts.

          1. Aileen Avatar
            Aileen

            Great, this works for me, thank you. My plugin is adding a custom post type, meta fields and variations to core blocks. Also a couple of custom blocks and patterns (which are only in use on the custom post type, so I only want the block css and js when the blocks are added, not everywhere).
            The build process is now working for the blocks and the other scripts too.
            Thanks for your help!

  4. Mark Solano Avatar
    Mark Solano

    Thanks this is helpful, especially for a beginner like me. While I was working on my interactivity api and followed the link that you have suggested. I have encountered a problem with my view.js that doesn’t get built on the build directory, or basically the code @wordpress/interactivity storing and registering a context doesn’t get compiled to any files which causes my block not to interact.

    How do I put my interactivity code in this kind setup?

    Thanks

    1. Troy Chaplin Avatar

      Hey Mark,

      I am glad you found this helpful. The issue with the view.js was noted under the “Removing Unused Files and Clean Up block.json” section, but as per the comments between myself and Aileen above I am hoping to find a solution to this issue.

      If you only want to compiles blocks and have no need for the extra scripts then you can follow the article right up to the “Combine Block Assets” section and blocks will still compile as you would expect in a basic create-block setup.

    2. Troy Chaplin Avatar

      Hey Mark,

      I have pushed updates to a branch that provides a method to build block as per the default build process will also including additional scripts for a variety of needs that output in separate files.

      I am not familiar with building anything with the Interactivity API, but as a way to test that with a multi block plugin I plan to look into this in the near future.

      You can find the latest branch here: https://github.com/wptrainingteam/building-multi-block-plugin/tree/DontCombineBlockScripts

  5. Huan Avatar

    Thank you Troy, multi-block is an important topic for devs.

    My use case is similar to Aileen:
    – separate scripts for interactive blocks (Interactivity API’s `–experimental-modules` instead of `–webpack-copy-php`)
    – non-block script file for core blocks’ filters

    I’m curious about the use cases for combined script? I thought separate scripts suits most use cases in a multi-block plugin.

    Looking forward to your solution. 🙂

    Discussions about webpack config:
    https://github.com/WordPress/gutenberg/issues/55936
    https://github.com/WordPress/gutenberg/discussions/44010

    1. Troy Chaplin Avatar

      Hey Huan,

      My use case was to serve one combined JS and CSS file via a CDN in a large scale environment. But also, I was not familiar with the impact of not having the view.js file was, as I have not had time to properly wrap my head around the Interactivity API. This was mentioned during the review of the article and a note about this was added. Since then I’ve been interested in finding a solution so people who find this article can make the choice between the 2 approaches.

      Thank you for the links, I will check those out later. The issues I currently face with the solution I am working on directly relates to webpack and what the build process is doing when I import the default config. I need to dive into that and see what other options I may have.

    2. Troy Chaplin Avatar

      Thanks again for those links Huan, I found the solution to the issue of how to build blocks using the default method while adding additional scripts in separate files.

      I would assume because the build is now default this would have no impact on the Interactivity API, but I hope to find some time soon to look into this and setup a test in this branch: https://github.com/wptrainingteam/building-multi-block-plugin/tree/DontCombineBlockScripts

      I’m not familiar with using –experimental-modules so I can’t say how this applies. Could this work in addition to –webpack-copy-php?

      Cheers!

      1. Huan Avatar

        Thanks Troy for your branch update. I see there are moving parts (`devDependencies` and ‘webpack.config.js’) and I’m afraid of breaking, so currently I put multi blocks in one plugin (following basic setup) and non-block scripts in another plugin.

        There seems no “WordPress standard” regarding webpack config for multi-block and non-block all-in-one, maybe your approach will be part of the standard. I try to raise awareness by sharing: https://github.com/WordPress/gutenberg/issues/55936#issuecomment-2376364088

Leave a Reply

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