WordPress.org

WordPress Developer Blog

Setting up a multi-block plugin using InnerBlocks and post meta

Setting up a multi-block plugin using InnerBlocks and post meta

WordPress has always been incredibly flexible and customizable. The Block and Site Editors are expanding our abilities to create custom experiences, and the ability to create custom blocks only enhances that ability.

Currently, the create-block utility only allows for creating one block at a time and doesn’t give the option for a multi-block setup. For example, what if you want to create two or more related blocks, especially where one custom block would use another to curate a user’s experience? What if you aim to use post meta to provide extra context to a user? These are common use cases and the area in which WordPress has always excelled. 

Below, you’ll learn how to structure a multi-block approach using the create-block utility.

Let’s keep that going by setting up the ability to review a post or custom post type (CPT) with a rating system and include that in another custom block that can be used in a Query Loop. This could be used with your content when creators publish reviews (books, movies, products) and need a rating system to display with their posts. The end result should look like this:

Specifically, you’ll build:

  1. A Review Card block with three specific inner blocks:
    1. Title (core/post-title)
    2. Rating block (a block we’ll make as well)
    3. Excerpt (core/post-excerpt)
  2. A Rating block that will:
    1. Rate a post (or CPT) from one to five stars or hearts
    2. Use post meta to store the rating
    3. Use the block in the Post Query block to show the rating of each post.

You’ll need a local environment set up and a terminal with NodeJS installed to run commands. If you don’t already have a local environment set up, you can use wp-now, LocalWP, or another option listed here that would suit your needs.

If you would like to follow along with the finished code, it is available in this Github repo and will be updated as needed; PRs welcome!

Setting up a multi-block plugin

Let’s start with setting up our project structure. In your terminal, cd into your wp-content/plugins directory. If you’re using wp-now you can work from whichever directory you like.

Scaffold the plugin files

Run npx @wordpress/create-block@latest post-review-blocks. This will scaffold a block plugin called “Post Review Blocks” in a directory named post-review-blocks. Go into that directory and open it in your code editor of choice. You should see a post-review-blocks.php file. Open that, and your code should look like the following (without the default comments):

/**
 * Plugin Name:       Post Review Blocks
 * Description:       Example block scaffolded with Create Block tool.
 * Requires at least: 6.1
 * Requires PHP:      7.0
 * Version:           0.1.0
 * Author:            The WordPress Contributors
 * License:           GPL-2.0-or-later
 * License URI:       https://www.gnu.org/licenses/gpl-2.0.html
 * Text Domain:       post-review-blocks
 *
 * @package CreateBlock
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit; // Exit if accessed directly.
}

function create_block_post_review_blocks_block_init() {
	register_block_type( __DIR__ . '/build );
}
add_action( 'init', 'create_block_post_review_blocks_block_init' );

The plugin itself should have a file structure like this:

- build/
- src/
- .editorconfig
- .gitignore
- .package-lock.json
- .package.json
- post-review-blocks.php
- readme.md

You will be working in the post-review-blocks.php file and src directory for this tutorial, and the build directory will get built automatically.

Scaffold the blocks

Next, delete all the files in the src directory so that the src is empty, and cd into the src if you’re not already there.

Run the following two commands:

npx @wordpress/create-block@latest review-card-block --no-plugin
npx @wordpress/create-block@latest rating-block --no-plugin --variant dynamic

This will create the two custom blocks needed for our multi-block setup. Note the  --no-plugin to both commands. The flag indicates that this is just creating the block files, not all the plugin files needed. Also, you can see the rating-block will be a “dynamic” block, rendering with PHP. Why? This allows you to get practice with both static and dynamic blocks.

Now, there are two blocks in our src folder:

  • rating-block
  • review-card-block

You can take care of a few more things:

  • Go into the block.json file for the rating-block and change the “icon” property from “smiley” to “star-filled”.
  • In both of the block.json files for each block, add the “keywords” property with "keywords": ["rating", "review"]. Your users will find the new blocks easier when searching.  
  • In the post-review-blocks.php file, update create_block_post_review_blocks_block_init to register both blocks, like this:
function create_block_post_review_blocks_block_init() {
	register_block_type( __DIR__ . '/build/rating-block' );
	register_block_type( __DIR__ . '/build/review-card-block' );
}
add_action( 'init', 'create_block_post_review_blocks_block_init' );

Build the blocks and activate the plugin

Now, from the root of the post-review-blocks plugin (the “root” is at the same level as the package.json file), run npm start,and the blocks should build into their own sub-directories in build. You can leave this script running for the remainder of the tutorial. Alternatively, you can stop and restart it if you want to run other commands or when you make changes to files like block.json. The script needs to be running for any changes to appear in the editor.

Once the build is successful, activate the plugin either in the WordPress dashboard or via WP-CLI with wp plugin activate post-review-blocks.

At this point, you can check that the blocks are registering by creating a new post and checking the inserter for the blocks by typing in one of the keywords we used:

Success! 🎉

You now know how to set up the structure of a multi-block plugin. You can add new blocks to the src folder with create-block and register their generated scripts in the build directory.

Now it’s time to add post meta functionality, assign inner blocks, and limit where the blocks can be used.

Registering post meta

Open up the post-review-blocks.php again, and paste the following after the create_block_post_review_blocks_block_init function:

// Add some post meta
function register_review_rating_post_meta() {
	$post_meta = array(
		'_rating'      => array( 'type' => 'integer'	),
		'_ratingStyle' => array( 'type' => 'string'	),
	);

	foreach ( $post_meta as $meta_key => $args ) {
		register_post_meta(
			'post',
			$meta_key,
			array(
				'show_in_rest'  => true,
				'single'        => true,
				'type'          => $args['type'],
				'auth_callback' => function() {
					return current_user_can( 'edit_posts' );
				}
			)
		);
	}
}
add_action( 'init', 'register_review_rating_post_meta' );

This function registers two post meta keys you will need:

  • _rating
  • _ratingStyle

These need to be registered. Otherwise, the data won’t be saved when you update our Rating block. You’ll also notice the two meta keys are prefixed with an underscore: _. This “protects” the meta from being used in the post’s custom fields section and potentially overwritten by the value in that meta box.

Finally, note that show_in_rest is set to true, and the auth_callback checks to make sure the user has at least edit_posts privileges. If the meta does not show up in the WordPress REST API, it cannot be edited in the block editor. 

A quick note beyond the scope of this tutorial: Be cautious with the data shown in the REST API. If you need to filter data out of the public API, do so in PHP elsewhere or choose a different method for working with sensitive data.

Building the Rating block

The Rating block allows your users to rate a post on a scale from one to five and choose between displaying a star emoji (⭐) or a heart emoji (❤️). You can copy these emojis into your code. With this functionality, the block accomplishes two objectives:

  1. Demonstrate how to save and pull from post meta
  2. Allow the post meta to be used in a Query Loop Post Template

There are many applications for this kind of functionality with CPTs, including:

  • Staff directories with email or position meta
  • Book catalogs with rating or ISBN meta
  • Recipe indexes with tastiness or prep_time meta

The possibilities are numerous!

Ok, all the edits in this section will be within the src/rating-block directory.

Add basic CSS

The following CSS is for the styles.scss file. Open that file, remove any CSS in there, and paste in the following, which adds padding around the block and ensures the star is yellow and the heart is red.

.wp-block-create-block-rating-block {
	padding: 1rem 0;
}

.wp-block-create-block-rating-block span.rating-star {
	color: yellow;
}

.wp-block-create-block-rating-block span.rating-heart {
	color: red;
}

Adjust these to your liking and save the file.

Add attributes and usesContext to the block.json file

Open up the block.json file for the Rating block and add the following properties:

"usesContext": ["postId", "postType"],
"attributes": {
	"rating": {
		"type": "integer",
		"default": 5
	},
	"ratingStyle": {
		"type": "string",
		"default": "star"
	}
},
"example": {
	"attributes": {
		"rating": 4,
		"ratingStyle": "star"
	}
}

The JSON above does the following:

  • usesContext: allows us to get the values of the post’s ID and type
  • attributes: identifies the properties to be saved on the block
  • example: gives a preview of what the block could look like when added

Click here to see the final block.json file.

Update the edit.js file

There is a lot to tackle. You can break it into three sections: 

  • Import and assignments
  • The functionality of retrieving and storing the post meta
  • Return method with the components in the editor sidebar
     

The full file can be found on GitHub.

Delete what is currently in the file. Then, at the top of the edit.js file, add the following:

import { __ } from "@wordpress/i18n";
import { useEffect } from "@wordpress/element";
import { useBlockProps, InspectorControls } from "@wordpress/block-editor";
import { PanelBody, RangeControl, SelectControl } from "@wordpress/components";
import { useEntityProp } from "@wordpress/core-data";

import "./editor.scss";

This section sets up our imports:

  • __ is our internationalization method
  • useEffect allows you to update metadata
  • useBlockProps gives you the block properties to work with
  • InspectorControls allows you to add controls to the Inspector sidebar
  • PanelBody, RangeControl, and SelectControl are all components to set up the user controls for the properties of the block
  • useEntityProp provides access to the post meta
  • And finally, the SCSS file is imported

Next, add the following: 

export default function Edit( {
	attributes: { rating, ratingStyle },
	setAttributes,
	context: { postType, postId },
} ) {
	const [meta, updateMeta] = useEntityProp(
		"postType",
		postType,
		"meta",
		postId,
	);

	// Add functionality code here

	// Add return() method here
	
	// Other code will go here, don't forget or delete the closing curly brace!
}

This is the Edit method, which controls what shows up in the block editor. First, you pass in and assign the following:

  • rating and ratingStyle get passed in and assigned to the attributes state object
  • setAttributes is the method by which the attributes state object gets updated
  • postType and postId are passed in with context from the usesContext you defined above

Next, you see that the state object for meta and the updateMeta method are getting assigned from the useEntityProp method, which is used by the block to get or change meta values.

Ok, so far so good? Now for the functionality! 

In place of the “Add functionality code here” comment, add the following methods:


useEffect(() => {
    const initStyle = meta?._ratingStyle ? meta?._ratingStyle : "star";
    setAttributes({
        rating: meta?._rating