WordPress.org

WordPress Developer Blog

Building a book review site with Block Bindings, part 1: Custom fields and block variations

Building a book review site with Block Bindings, part 1: Custom fields and block variations

Imagine your agency just picked up a new client. This client runs a book review website that needs a complete overhaul. They need to add some custom fields to associate with each book like a rating, author, length, and a fancy link to Goodreads.

Your agency is also transitioning from classic-based WordPress development to fully embracing everything the Block Editor has to offer. But the job parameters aren’t going to let you build custom blocks for every bit of metadata and customization the client needs. You need a solution that’s a bit more efficient.

That’s where the Block Bindings API comes in. I covered a basic introduction to this API in two previous posts:

But the true power of Block Bindings is in combination with various other APIs and systems within WordPress, such as Block Variations, Patterns, and more. Once you start piecing them together, the potential for what you can achieve atop the block system feels nearly limitless.

So let’s build the foundation of your client’s book review site together. Then, I’ll let you take over and fine tune it.

Prerequisites

This tutorial will cover a lot of ground, and I cannot explain every line of code in detail without it becoming overly complex. Therefore, you’ll need at least a cursory understanding of the following concepts. The goal of this series is to show how the various APIs in WordPress can be used in unison.

  • Node and npm: You must have Node.js and npm installed on your local machine to follow this tutorial.
  • JavaScript programming: You should have some baseline knowledge necessary to read, write, and understand intermediate JavaScript.
  • Block Editor development: You should feel comfortable building code that runs in the WordPress Block Editor.
  • Theme development: You should be able to build a block theme, work from its functions.php file, and set up a build process.
  • Build tools: You must know how to write commands from a Command Line Interface (CLI), such as npm run <command>.

Setting up your project

For this project, you will create a child theme for Twenty Twenty-Four named TT4 Book Reviews. This is so that you don’t have to recreate every part of the theme just to get things rolling. You can view the fully built example via its repository.

I suggest importing the example content directly into a clean development install. Note that the meta keys used for the posts have the themeslug_ prefix as used throughout this tutorial.

There is also a live demo using WordPress Playground if you want to see the final result:

File structure

Create a new folder named tt4-book-reviews in your /wp-content/themes directory. Also, go ahead and add the following sub-folders and files within your theme:

  • /patterns
  • /public
  • /resources
    • /js
      • editor.js
      • meta.js
      • query.js
      • variations.js
  • /templates
  • functions.php
  • package.json
  • style.css
  • webpack.config.js

This may seem like a lot, but most of the files that you’ll add have very little code. They will just be separated for their specific purpose.

The main stylesheet and functions file

Because you’re building this as a child theme of the default Twenty Twenty-Four theme, you first need to create your style.css file in your theme folder and add a file header so WordPress recognizes it.

Add this code to your style.css file:

/*!
 * Theme Name:        TT4 Book Reviews
 * Description:       A child theme for a book review site.
 * Version:           1.0.0
 * Template:          twentytwentyfour
 * Tags:              full-site-editing
 * Text Domain:       themeslug
 * Tested up to:      6.5
 * Requires at least: 6.5
 * Requires PHP:      7.4
 * License:           GNU General Public License v2.0 or later
 * License URI:       https://www.gnu.org/licenses/gpl-2.0.html
 */

You’ll also be working with functions.php throughout this tutorial, so it’s a good idea to get this file set up too.

Add this code to your theme’s functions.php file:

<?php
// Add your custom code below here.

Now visit the Appearance  > Themes screen in your WordPress admin, locate your new TT4 Book Reviews theme, and activate it.

Build scripts

This tutorial requires some JavaScript, so it’s also a good idea to set up a build process at this early stage. This process has been previously covered in depth in Beyond block styles, part 1: using the WordPress scripts package with themes. It is also described in the Build Process documentation in the Theme Handbook.

Open your theme’s package.json file to add a project name and these scripts:

{
	"name": "your-project-name",
	"scripts": {
		"start": "wp-scripts start --webpack-src-dir=resources --output-path=public",
		"build": "wp-scripts build --webpack-src-dir=resources --output-path=public"
	}
}

Now, navigate to your wp-content/themes/tt4-book-reviews directory on your computer via its command line program and type the following command to install the necessary packages:

npm install @wordpress/scripts @wordpress/icons path webpack-remove-empty-scripts --save-dev

Your package.json file should now look like this:

{
	"name": "your-project-name",
	"scripts": {
		"start": "wp-scripts start --webpack-src-dir=resources --output-path=public",
		"build": "wp-scripts build --webpack-src-dir=resources --output-path=public"
	},
	"devDependencies": {
		"@wordpress/icons": "^9.46.0",
		"@wordpress/scripts": "^27.6.0",
		"path": "^0.12.7",
		"webpack-remove-empty-scripts": "^1.0.4"
	}
}

There’s one more step that you’ll need to take to get the build process working with your theme, and that is setting up your theme’s webpack configuration file. For this tutorial, you’ll only need this for building editor scripts.

Open your theme’s webpack.config.js file and add this code to it:

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

module.exports = {
	...defaultConfig,
	...{
		entry: {
			'js/editor':  path.resolve( process.cwd(), 'resources/js', 'editor.js'   )
		},
		plugins: [
			// Include WP's plugin config.
			...defaultConfig.plugins,

			// Removes the empty `.js` files generated by webpack but
			// sets it after WP has generated its `*.asset.php` file.
			new RemoveEmptyScriptsPlugin( {
				stage: RemoveEmptyScriptsPlugin.STAGE_AFTER_PROCESS_PLUGINS
			} )
		]
	}
};

Registering custom fields

Before even thinking about connecting custom fields to blocks, you must first register each meta key that you want to use. For this book review site, let’s stick to four fields that your client would need:

  • themeslug_book_author: The book’s author name.
  • themeslug_book_rating: The user’s star rating for the book.
  • themeslug_book_length: The number of pages in the book.
  • themeslug_book_goodreads_url: The URL to the book’s page on Goodreads.com.

With the meta keys decided, it’s time to register them via the standard register_meta() function. Add this code to your theme’s functions.php file:

add_action( 'init', 'themeslug_register_meta' );

function themeslug_register_meta() {
	register_meta( 'post', 'themeslug_book_author', [
		'show_in_rest'	    => true,
		'single'            => true,
		'type'              => 'string',
		'sanitize_callback' => 'wp_filter_nohtml_kses'
	] );

	register_meta( 'post', 'themeslug_book_rating', [
		'show_in_rest'      => true,
		'single'            => true,
		'type'              => 'string',
		'sanitize_callback' => 'wp_filter_nohtml_kses'
	] );

	register_meta( 'post', 'themeslug_book_length', [
		'show_in_rest'      => true,
		'single'            => true,
		'type'              => 'string',
		'sanitize_callback' => 'wp_filter_nohtml_kses'
	] );

	register_meta( 'post', 'themeslug_book_goodreads_url', [
		'show_in_rest'      => true,
		'single'            => true,
		'type'              => 'string',
		'sanitize_callback' => 'esc_url_raw'
	] );
}

For these fields to work alongside the Block Bindings API, you need to ensure that each registered meta key of both of these arguments is set to true:

  • show_in_rest: This is necessary for the custom fields to be usable over the REST API, which the Block Editor uses.
  • single: Multiple meta values for a single key are not currently supported by the Block Bindings API, so this needs to be set to a single value.

The keen-eyed among you may have noticed that a couple of the meta keys that are integers are defined as strings: themeslug_book_rating and themeslug_book_length. This is an unfortunate limitation of getting the values to appear in the editor when used with blocks that require stringed attributes. We’ll work around this later when we get into the meta input controls.

Problems and solutions: Connecting custom fields to blocks

WordPress 6.5+ supports connecting custom fields to blocks via the core/post-meta binding source by default. This means that you don’t have to worry about registering anything via the Block Bindings API.

But, as described in Introducing Block Bindings, part 1: connecting custom fields, the only way to insert a bound block into the editor is by manually switching to the Code Editor view and typing out the block markup.

For example, here is what the markup looks like when binding the themeslug_book_author custom field to the Paragraph block:

<!-- wp:paragraph {
	"metadata":{
		"bindings":{
			"content":{
				"source":"core/post-meta",
				"args":{
					"key":"themeslug_book_author"
				}
			}
		}
	}
} -->
<p></p>
<!-- /wp:paragraph -->

And the only way to input custom meta values is by enabling the Custom Fields panel and manually typing the keys and values:

As a reputable developer, you would never want to hand this experience over to a client and ask them to put in all that work. But these limitations were expected for the first version of the Block Bindings API. There wasn’t enough time to build a UI and perfect the user experience before the WordPress 6.5 launch.

A more fine-tuned user experience is expected for the Block Bindings API in the future, but it will happen over multiple WordPress releases. Subscribe to the Block Bindings API tracking ticket to follow along or get involved with the process.

As a developer, what can you do to start using—I mean really using—this API for real-world projects that you will pass over to clients? That’s the question that I seek to answer with this tutorial series, and the following sections will dive into solutions.

Using block variations to insert bound blocks

What if you could insert the bound blocks like any other block with no code editing involved? You can absolutely do this by registering a variation via the Block Variations API.

A block variation is nothing more than an alternative version of a block with a different set of default attributes than the original block. And a bound block is simply a block with a defined metadata.bindings attribute.

That means that it’s possible to combine these two concepts. For example, you can see variations that represent the custom fields you registered earlier in this screenshot:

Instead of manually typing block markup each time you want to bind a block’s attributes, you can set up a variation for inserting it just like any other block. That will be much less of a headache for both you and your client.

Registering variations for bound blocks

To integrate bindings and variations, you must register your variations via JavaScript (the PHP registration method doesn’t support the isActive() check needed for this technique).

If you haven’t already done so, kick-start the build process by typing the following in your command line program:

npm run start

This will generate the build files needed for the project under the /public folder that you created earlier.

Before diving into JavaScript, you must first enqueue your newly generated /public/js/editor.js file in the editor. Add this code to your functions.php file:

add_action( 'enqueue_block_editor_assets', 'themeslug_editor_assets' );

function themeslug_editor_assets() {
	$script_asset = include get_theme_file_path( 'public/js/editor.asset.php' );

	wp_enqueue_script(
		'themeslug-editor',
		get_theme_file_uri( 'public/js/editor.js' ),
		$script_asset['dependencies'],
		$script_asset['version'],
		true
	);
}

Now add this code to your editor.js file in /resources/js to import the empty variations.js script:

import './variations';

With this code in place, you can begin registering custom variations for any block that you want to bind to a custom field. For this tutorial, you will bind each of your custom fields to a single block attribute:

Custom FieldBlockAttribute
themeslug_book_authorParagraphcontent
themeslug_book_ratingParagraphcontent
themeslug_book_lengthParagraphcontent
themeslug_book_goodreads_urlButtonurl

Of course, you can bind your custom fields to any number of blocks that you need for your project.

First, open your variations.js file and add the following code to import the dependencies you’ll need and to define a Goodreads icon:

import { registerBlockVariation } from '@wordpress/blocks';
import { __ } from '@wordpress/i18n';
import { page, pencil, starFilled } from '@wordpress/icons';

const goodreadsIcon = (
	<svg width="24" height="24" viewBox="0 0 24 24" version="1.1">
		<path d="M17.3,17.5c-0.2,0.8-0.5,1.4-1,1.9c-0.4,0.5-1,0.9-1.7,1.2C13.9,20.9,13.1,21,12,21c-0.6,0-1.3-0.1-1.9-0.2 c-0.6-0.1-1.1-0.4-1.6-0.7c-0.5-0.3-0.9-0.7-1.2-1.2c-0.3-0.5-0.5-1.1-0.5-1.7h1.5c0.1,0.5,0.2,0.9,0.5,1.2 c0.2,0.3,0.5,0.6,0.9,0.8c0.3,0.2,0.7,0.3,1.1,0.4c0.4,0.1,0.8,0.1,1.2,0.1c1.4,0,2.5-0.4,3.1-1.2c0.6-0.8,1-2,1-3.5v-1.7h0 c-0.4,0.8-0.9,1.4-1.6,1.9c-0.7,0.5-1.5,0.7-2.4,0.7c-1,0-1.9-0.2-2.6-0.5C8.7,15,8.1,14.5,7.7,14c-0.5-0.6-0.8-1.3-1-2.1 c-0.2-0.8-0.3-1.6-0.3-2.5c0-0.9,0.1-1.7,0.4-2.5c0.3-0.8,0.6-1.5,1.1-2c0.5-0.6,1.1-1,1.8-1.4C10.3,3.2,11.1,3,12,3 c0.5,0,0.9,0.1,1.3,0.2c0.4,0.1,0.8,0.3,1.1,0.5c0.3,0.2,0.6,0.5,0.9,0.8c0.3,0.3,0.5,0.6,0.6,1h0V3.4h1.5V15 C17.6,15.9,17.5,16.7,17.3,17.5z M13.8,14.1c0.5-0.3,0.9-0.7,1.3-1.1c0.3-0.5,0.6-1,0.8-1.6c0.2-0.6,0.3-1.2,0.3-1.9 c0-0.6-0.1-1.2-0.2-1.9c-0.1-0.6-0.4-1.2-0.7-1.7c-0.3-0.5-0.7-0.9-1.3-1.2c-0.5-0.3-1.1-0.5-1.9-0.5s-1.4,0.2-1.9,0.5 c-0.5,0.3-1,0.7-1.3,1.2C8.5,6.4,8.3,7,8.1,7.6C8,8.2,7.9,8.9,7.9,9.5c0,0.6,0.1,1.3,0.2,1.9C8.3,12,8.6,12.5,8.9,13 c0.3,0.5,0.8,0.8,1.3,1.1c0.5,0.3,1.1,0.4,1.9,0.4C12.7,14.5,13.3,14.4,13.8,14.1z" />
	</svg>
);

From this point, you must register variations for each of the bound blocks. Start by creating a variation for the Paragraph block named “Book Author”:

registerBlockVariation( 'core/paragraph', {
	name:       'themeslug/book-author',
	title:      __( 'Book Author', 'themeslug' ),
	description: __( 'Displays the book author.', 'themeslug' ),
	category:   'widgets',
	keywords:   [ 'book', 'author' ],
	icon:       pencil,
	scope:      [ 'inserter' ],
	attributes: {
		metadata: {
			bindings: {
				content: {
					source: 'core/post-meta',
					args: {
						key: 'themeslug_book_author'
					}
				}
			}
		},
		placeholder: __( 'Book Author', 'themeslug' )
	},
	example: {},
	isActive: (blockAttributes) =>
		'themeslug_book_author' === blockAttributes?.metadata?.bindings?.content?.args?.key
});

For the most part, you can define this variation however you like, but there are two properties that you need to pay special attention to:

  • attributes:
    • metadata: You must set the bindings property in the same way that you’d define it at the block level with the core/post-meta source and the associated post meta key (themeslug_book_author for the above variation).
    • placeholder: (Optional) When setting a placeholder, the text will appear in the editor whenever the user hasn’t yet set a meta value.
  • isActive(): This callback checks if the block has the current variation. Therefore, it checks if the defined metadata.bindings.content.args.key value exists and matches the post meta key.

Now repeat the same process for the Book Length, Book Rating, and Book Goodreads Button variations:

registerBlockVariation( 'core/paragraph', {
	name:       'themeslug/book-length',
	title:      __( 'Book Length', 'themeslug' ),
	description: __( 'Displays the book length in pages.', 'themeslug' ),
	category:   'widgets',
	keywords:   [ 'book', 'pages', 'length' ],
	icon:       page,
	scope:      [ 'inserter' ],
	attributes: {
		metadata: {
			bindings: {
				content: {
					source: 'core/post-meta',
					args: {
						key: 'themeslug_book_length'
					}
				}
			}
		},
		placeholder: __( 'Book Length', 'themeslug' )
	},
	example: {},
	isActive: (blockAttributes) =>
		'themeslug_book_length' === blockAttributes?.metadata?.bindings?.content?.args?.key
});

registerBlockVariation( 'core/paragraph', {
	name:       'themeslug/book-rating',
	title:      __( 'Book Rating', 'themeslug' ),
	description: __( 'Displays the book rating.', 'themeslug' ),
	category:   'widgets',
	keywords:   [ 'book', 'rating', 'review' ],
	icon:       starFilled,
	scope:      [ 'inserter' ],
	attributes: {
		metadata: {
			bindings: {
				content: {
					source: 'core/post-meta',
					args: {
						key: 'themeslug_book_rating'
					}
				}
			}
		},
		placeholder: __( 'Book Rating', 'themeslug' )
	},
	example: {},
	isActive: (blockAttributes) =>
		'themeslug_book_rating' === blockAttributes?.metadata?.bindings?.content?.args?.key
});

registerBlockVariation( 'core/button', {
	name:       'themeslug/book-goodreads-button',
	title:      __( 'Book Goodreads Button', 'themeslug' ),
	description: __( 'Displays a button with the link to the Goodreads book URL.', 'themeslug' ),
	category:   'widgets',
	keywords:   [ 'book', 'author' ],
	icon:       goodreadsIcon,
	scope:      [ 'inserter' ],
	attributes: {
		text: __( 'View on Goodreads &rarr;', 'themeslug' ),
		metadata: {
			bindings: {
				url: {
					source: 'core/post-meta',
					args: {
						key: 'themeslug_book_goodreads_url'
					}
				}
			}
		}
	},
	example: {},
	isActive: (blockAttributes) =>
		'themeslug_book_goodreads_url' === blockAttributes?.metadata?.bindings?.url?.args?.key
});

From this point forward, you no longer need to manually type block bindings into the Code Editor. You can insert them just like you would any other block.

Adding meta input controls

There’s still one major problem to solve. To enter custom field values, users must know how to turn on the Custom Fields panel in the editor and correctly type both the key and value fields.

Let’s fix that by adding custom controls in the sidebar like this:

First, add this code to your editor.js file in /resources/js to import the empty meta.js script:

import './meta';

Then open your meta.js file and import the following dependencies:

import { useEntityProp } from '@wordpress/core-data';
import { useSelect } from '@wordpress/data';
import { PluginDocumentSettingPanel } from '@wordpress/edit-post';
import { __ } from '@wordpress/i18n';
import { starFilled } from '@wordpress/icons';
import { registerPlugin } from '@wordpress/plugins';

import {
	RangeControl,
	__experimentalInputControl as InputControl,
	__experimentalNumberControl as NumberControl,
	__experimentalVStack as VStack
} from '@wordpress/components';

The above code uses a few experimental components, which are needed to add the custom controls for meta input. There is currently an open tracking ticket that would remove the experimental designation from each of those that we’re using. If you’re uncomfortable using experimental components, you can always create the inputs using custom components.

To add controls to the post sidebar, the best option is to use the registerPlugin() JavaScript function and its render callback property. Don’t worry, you’re not registering a plugin that users must install and activate. You’re creating an editor plugin.

Open meta.js and add the following code:

registerPlugin( 'tt4-book-reviews', {
	render: () => {
		// Add your custom code here.
	}
} );

From this point forward, your remaining code will go inside of the render callback.

Since you wouldn’t want your custom controls to be output for every instance of the editor, you first need to check the current post type to determine if this is the correct editor. You can do this with the useSelect() hook. In this case, you only want the controls to appear when post is the current post type. If it’s not, the render callback will return null

You’ll also need to use the useEntityProp() hook to get and set meta values by assigning a couple of constants: meta and setMeta.

Add this code inside your render callback:

const postType = useSelect(
	( select ) => select( 'core/editor' ).getCurrentPostType(),
	[]
);

const [ meta, setMeta ] = useEntityProp( 'postType', postType, 'meta' );

if ( 'post' !== postType ) {
	return null;
}

From this point, it’s just a matter of returning the components to output the meta controls. The most important thing is to use the PluginDocumentSettingsPanel SlotFill so that they are added to the correct location in the editor. 

For this sidebar panel, you’ll use the following components and associate them with your meta keys:

Add the following code inside your render callback:

return (
	<PluginDocumentSettingPanel
		title={ __( 'Book Review', 'themeslug' ) }
	>
		<VStack>
			<RangeControl
				label={ __( 'Rating', 'themeslug' ) }
				beforeIcon={ starFilled }
				currentInput={0}
				initialPosition={0}
				min={ 0 }
				max={ 5 }
				step={ 1 }
				value={ parseInt( meta?.themeslug_book_rating, 10 ) }
				onChange={ ( value ) => setMeta( {
					...meta,
					themeslug_book_rating: `${ value }` || null
				} ) }
			/>
			<InputControl
				label={ __( 'Author', 'themeslug' ) }
				value={ meta?.themeslug_book_author }
				onChange={ ( value ) => setMeta( {
					...meta,
					themeslug_book_author: value || null
				} ) }
			/>
			<NumberControl
				label={ __( 'Total Pages', 'themeslug' ) }
				min={ 0 }
				value={ parseInt( meta?.themeslug_book_length, 10 ) }
				onChange={ ( value ) => setMeta( {
					...meta,
					themeslug_book_length: `${ value }` || null
				} ) }
			/>
			<InputControl
				label={ __( 'Goodreads URL', 'themeslug' ) }
				value={ meta?.themeslug_book_goodreads_url }
				onChange={ ( value ) => setMeta( {
					...meta,
					themeslug_book_goodreads_url: value || null
				} ) }
			/>
		</VStack>
	</PluginDocumentSettingPanel>
);

There are two values I want you to pay attention to in the above code for the Rating and Total Pages controls:

  • value
  • onChange

Those controls handle integer values but pass strings back when setting the meta. Take a look at how these are handled for the Rating:

value={ parseInt( meta?.themeslug_book_rating, 10 ) }
onChange={ ( value ) => setMeta( {
	...meta,
	themeslug_book_rating: `${ value }` || null
} ) }

Note that it uses parseInt() for the value property to pass an integer to the control. For the onChange property, the value variable is passed a template literal so that it’s treated as a string. This is necessary for the value to show in the editor and be saved correctly.

As you add data to the meta fields, you should see them automatically appear for any bound blocks in the content area of the editor:

The next steps

I would consider what we’ve accomplished thus far to be the “hard” part. You now have all the pieces in place to do the fun stuff like front-end design work.

Stay tuned into the Developer Blog for the next post in this series. Some items I’ll cover next include:

  • Displaying posts via the Query Loop block by meta.
  • Integrating custom fields into your theme templates.
  • Building patterns for displaying custom fields.

Props to @bph and @ndiego for reviewing this tutorial and @welcher for code examples.

20 responses to “Building a book review site with Block Bindings, part 1: Custom fields and block variations”

  1. Mrinal Haque Avatar

    This is a complete learning package about the Block Bindings API feature. Great!

  2. codersantosh Avatar

    Thank you for the article, it’s really useful! Using block variations seems user-friendly for normal users for binding blocks. I have a couple of questions:

    1. Doesn’t using block variations with metadata make it plugin territory?
    2. Can we include CSS/JavaScript specifically for the block variation or binding we’re using?

    1. Justin Tadlock Avatar

      1. Block Variations are not generally considered either plugin or theme territory. They’re just variations of the existing block attributes. As for non-design meta, it’s usually considered plugin territory if you’re building a publicly-distributed theme. Because this post is specifically discussing client work, I thought it’d be best to not attempt to teach both plugin and theme dev in a single post.

      2. I don’t think there’s a built-in mechanism for per-variation assets, but you could always hook into render_{block_name}_block, check its attributes, and enqueue only if those attributes match on the front end.

      1. codersantosh Avatar

        Thank you for responding.
        I’ve also observed that block-binding changes are only visible on the front end and within the editor, they’re merely represented as placeholders with no editing access.

        1. Justin Tadlock Avatar

          I’d have to see your code (feel free to DM me on Slack), but that’s usually when there’s either a type conflict between the meta and the block attribute or something that needs to be set when registering the meta.

  3. T. Avatar

    I’m reading your book Professional WordPress Plugin Development.

    I get to the last chapter where you listed this as one of the places the reader should check out.

    Very first post i see is from the author. 👍🏽

  4. Markus Avatar
    Markus

    Block binding sounds quite promising, but I don’t think it is helping the mess the block-editor still is.
    It is a good thing that some things are possible with it be ‘configuration’ rather than by programming a new block from scratch.
    Still, this nothing an amateur will likely to be able to work with. And programmers (like me) won’t want to use it, simply because the block-editor basically isn’t really usable for commercial projects — at least not for me and for plenty of colleagues — at least not in an economically sensible way.
    The reason is simply that locking down templates/patterns is more or less impossible.
    My clients want a consistently designed website, that is easy to update and maintain, even for less tech-savvy people.
    That was rather easy to implement with the classic editor, custom post-types and (advanced-)custom-fields. The editor entered all the necessary data-fields, and the theme took care of the design. Granted, with in Tiny-MCE they could make some mess, but usually that wasn’t a problem.
    The way templates and patterns are supposed to work, the editor con mess up everything — either accidentally, or deliberately. There is no easy way to disable the toolbar of a block in favor of a default setting as given by the designer. So I either have to make sure that my CSS and output HTML can deal with unwanted/forbidden layout options, or I have to ignore user choices, offered by the UI.

    My point is, that while you might be doing an impressive job in enabling ‘professional’ options for blocks, trying to remedy the inherent shortcomings of page-builder; all this is worth nothing (to me and others) as long as we have to go through the annoying process of building a new block every time we need to secure structure and design.

    Take the mentioned block-variations, for example: It is not possible to deactivate them via PHP, as would be expected, it only works at the front end (and it does not really half of the time). And that they have no inherent way to use different CSS is unbelievable. Your suggestion to use a render hook will surely work, but makes for an even greater mess than the whole react based block system already is.

    Please excuse my rant, but I’m really frustrated with the situation.

    1. Justin Tadlock Avatar

      This is a discussion that’s probably best suited to the #outreach channel on Making WordPress Slack. I encourage you to join everyone there to kick-start these larger conversations.

      I’ll avoid the overly opinionated things, which have not been consistent with my experience, but I’ll try my best to respond to any technical issues.

      The reason is simply that locking down templates/patterns is more or less impossible.

      There are multiple ways to lock templates/patterns down or curate the experience. Without knowing the specifics of the scenario, I usually recommend starting with Block Locking.

      There is no easy way to disable the toolbar of a block in favor of a default setting as given by the designer. So I either have to make sure that my CSS and output HTML can deal with unwanted/forbidden layout options, or I have to ignore user choices, offered by the UI.

      Block Locking might come in handy here too. You can also control UI tools through theme.json settings. Again, this really depends on the specifics of the situation.

      Take the mentioned block-variations, for example: It is not possible to deactivate them via PHP, as would be expected, it only works at the front end (and it does not really half of the time).

      You can unregister them via JavaScript with one line of code.

      And that they have no inherent way to use different CSS is unbelievable. Your suggestion to use a render hook will surely work, but makes for an even greater mess than the whole react based block system already is.

      My suggestion above was related to loading custom scripts or stylesheets for variations. You can absolutely use variation-specific CSS. You can just pass a custom class to attributes.className for your variation and style it. Personally, I prefer to couple that with Block Styles, but that’s not a hard requirement.

      1. Markus Avatar
        Markus

        Thank you for your answer and tolerating my criticism.
        All of your answers above are correct, and I knew of them beforehand, but actually none of those solutions really work:

        Block or theme-locking have a lot of issues, some of them being ignored for years. (https://github.com/WordPress/gutenberg/issues/11681, https://github.com/WordPress/gutenberg/issues/44231)
        And even if the ones existing would work as expected (which they often don’t), there still is no mechanism for something basic as “at least one, at most five”

        In theme.json you can only disable very few options for most blocks and there is no simple way to lock/enforce presets like alignment…

        Unregistering block-variations only works via JavaScript, and that might be one reason why it doesn’t work really. I have a group (default) in my template, I unregister group row and column and I cannot add new ones of those, but the one that is generated from my template can still be switched to row or column.

        My point still being that there are too many half-baked solutions and concepts, and I don’t see any real efforts to address or even acknowledge those. The whole lacks a consistent architecture in my opinion and is way overcomplicated. (I’m convinced that react is an important cause of that problem.)

        But I will consider joining the #outreach channel and stop pestering you with my frustrations.

  5. Markus Avatar
    Markus

    I forgot: custom-fields are really a superpower of WordPress in my opinion and there are plenty of good reasons to utilize them. But every custom field has one value for one post. So any block that would handle ‘themeslug_book_rating’ on that post’s page would show the same data. It would be very useful therefore to limit the use of that block to one instance, but even so, there is a mechanism for that it requires (as far as I understand) implementation in the block.json? And therefore a specially programmed block?
    Moreover, you did (logically) put the inputs for the field values in the sidebar, but also suggested that it might be a solution to handle that in a custom block. (I’d guess in pace would be the obvious approach.) That, however, makes it even more necessary to have a well-working flexible template locking/restricting system.
    My point again: the longstanding obvious shortcomings and half-baked basic concepts need to be fixed for block-binding to be of real use.

    1. Justin Tadlock Avatar

      But every custom field has one value for one post. So any block that would handle ‘themeslug_book_rating’ on that post’s page would show the same data. It would be very useful therefore to limit the use of that block to one instance, but even so, there is a mechanism for that it requires (as far as I understand) implementation in the block.json? And therefore a specially programmed block?

      If you need to build a block with only a single instance, you can set supports.multiple in block.json to false. But, yes, that’s something that’s defined at the block level itself.

      I’d probably want to go with a Block Locking approach again here if you want to limit it alongside variations. Or even a template lock at the post type level.

      Moreover, you did (logically) put the inputs for the field values in the sidebar, but also suggested that it might be a solution to handle that in a custom block. (I’d guess in pace would be the obvious approach.)

      We also have a post here on the Developer Blog that explains that approach: Creating a custom block that stores post meta.

      1. Markus Avatar
        Markus

        Actually trying something similar is the cause of my current surge of frustration. Only one of a myriad of combinations of template lock and block-locking made it possible to get at least halfway to my goal of fixing that custom-field block on the top of the template; without getting the annoying unhelpful message (https://github.com/WordPress/gutenberg/issues/11681)
        I combined it with a group that should hold various other flexible contents inside, but even locked down as good as it gets, both blocks always showed the toolbar with useless (or unwanted) options.

  6. Paul Avatar
    Paul

    How would you validate the goodreads URL if you wanted to give immediate feedback to the editor that only goodreads urls are allowed

    1. Paul Avatar
      Paul

      I thought I could use standard html attributes like type=url and pattern, and get the free validation that provides, but it doesn’t seem to be supported.so I added some custom logic with useEffect and useState to toggle the post locking and display the error message below the field

  7. Marty O'Connor Avatar
    Marty O’Connor

    Note:
    When registering the button variation (Book Goodreads Button), I had to change:

    registerBlockVariation( ‘core/button’,…
    to:
    registerBlockVariation( ‘core/buttons’,…

    In order to target the buttons block and be able to see the block in the editor.

    1. Justin Tadlock Avatar

      That would make it not work with Block Bindings because the variation is for the inner Button block (this is a single button), not the containing Buttons block (this is for a group of buttons).

      1. Marty O'Connor Avatar
        Marty O’Connor

        Thank you!

  8. Charlie Benger-Stevenson Avatar
    Charlie Benger-Stevenson

    I really want this to work. But it isn’t. I have the child theme installed and activated but no custom block elements are appearing in the editor. I am following along very closely and have got to the part where it says “From this point forward, you no longer need to manually type block bindings into the Code Editor. You can insert them just like you would any other block.”

    No this is not happening. What could I be doing wrong?
    The meta keys match perfectly.

    1. Justin Tadlock Avatar

      You should be able to test the code from the tutorial Playground demo to see it in action. That should give you an idea of how it should work.

      But please reach out on the Outreach Slack channel, and we can see about walking through your specific situation.

  9. Drake Gomez Avatar
    Drake Gomez

    Thank you, Justin, for this thorough tutorial. For a project I’m working on, I’ve added a sidebar input control for the URL of an image block. This works fine, but I’m wondering if there’s a component that would allow the user to select from the Media Library rather than having to paste the URL.

Leave a Reply

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