Rethinking Meta Boxes with the new WordPress block editor: Part 1

The post has been updated to reflect some code improvements pointed out by Daniel Bachhuber.

I have spoken to a number of agencies, and WordPress developers, and I have come to one conclusion; ACF and Meta Boxes are critical for their deliveries. ACF, particularly the pro version, seems to be used quite a bit. Most of the work I do does not use ACF, but I certainly have used meta boxes a fair bit. We have probably done all sorts of crazy stuff with meta boxes and they have served us well. The new block editor can be somewhat scary as it appears to be replacing meta boxes. In fact, one of the goals of blocks is to replace things like meta boxes. Metadata management is definitely a crucial selling point for WordPress to online businesses. There seems to be the thought that the new block editor will ruin WordPress as a CMS because we are no longer dealing with structured data. I don't think this is the case at all, blocks will bring even more structure than currently exists, hopefully after reading this you will be excited to make the leap to Gutenberg. The tricky part is going to be the transition.

Making the leap to Gutenberg

The new block editor brings a couple major changes to the world of WordPress development. The block API that powers it, seeks to consolidate many elements of WordPress. The editor portion, is a relatively large React/Redux application powered by JavaScript. WordPress development has largely been fragmented across many APIs and I think the new block API, once it is fully fleshed out, will help bring about a better set of standards, and allow developers to create more engaging experiences in less time. You can already ship fast with WordPress, but once the new block editor is running in full gear, I feel it will greatly accelerate the rate at which people create things with WordPress.

The leap will hopefully not have to be painful thanks to the work being done by the Gutenberg team. Part of the goal is to make sure the transition is as smooth as possible. Support for meta boxes, shortcodes, and other features are already included. The meta box support is also continually being improved upon. It is important to not just force every developer to rewrite all of the work they have done in JavaScript. Instead there will be an indefinite period where the block editor will have near complete backwards compatibility, which is really a tremendous feat that should be praised. So big shout out to Riad Benguella and Andrew Duthie who have really been hauling ass on this project! Give them some love. Speaking of Riad, I highly recommend checking out his post about Gutenberg and meta boxes.

If there is backwards compatibility in place though, what is the big deal with blocks and the block editor, why should I rewrite things as a block? Well, it is honestly not fully apparent why blocks matter. Let's quickly compare the two and hopefully I can pique your interest.

A basic meta box implemented in the classic WordPress way

plugin.php

<?php
/*
 * Plugin Name: A classic meta block.
 * Version: 0.1.0
 * Author: Edwin Cromley
 * Author URI: https://edwincromley.com
 * License: GPL3+
 *
 * Description: Random meta box.
 */


add_action( 'add_meta_boxes', 'sample_add_metabox' );
add_action( 'save_post', 'sample_save_metabox', 10, 2 );

/**
 * Adds the meta box.
 */
function sample_add_metabox() {
	add_meta_box(
		'post-notes',
		__( 'Post Notes', 'textdomain' ),
		'sample_render_metabox',
		'post',
		'normal',
		'default'
	);
}

/**
 * Renders the meta box.
 */
function sample_render_metabox( $post ) {
	// Add nonce for security and authentication.
	wp_nonce_field( 'custom_nonce_action', 'custom_nonce' );

	$notes = get_post_meta( $post->ID, 'notes', true );

	?>
	<textarea style="width: 100%; height: 200px;" name="notes">
		<?php echo esc_html( $notes ); ?>
	</textarea>
	<?php
}

/**
 * The code below will not run because my host blocks writing
 * super globals in anyways shape or form. So pretend $POST is
 * the actual superglobal.
 */

/**
 * Handles saving the meta box.
 *
 * @param int     $post_id Post ID.
 * @param WP_Post $post    Post object.
 * @return null
 */
function sample_save_metabox( $post_id, $post ) {
	// Add nonce for security and authentication.
	$nonce_name   = isset( $POST['custom_nonce'] ) ? $POST['custom_nonce'] : '';
	$nonce_action = 'custom_nonce_action';

	// Check if nonce is set.
	if ( ! isset( $nonce_name ) ) {
		return;
	}

	// Check if nonce is valid.
	if ( ! wp_verify_nonce( $nonce_name, $nonce_action ) ) {
		return;
	}

	// Check if user has permissions to save data.
	if ( ! current_user_can( 'edit_post', $post_id ) ) {
		return;
	}

	// Check if not an autosave.
	if ( wp_is_post_autosave( $post_id ) ) {
		return;
	}

	// Check if not a revision.
	if ( wp_is_post_revision( $post_id ) ) {
		return;
	}

	if ( ! isset( $POST['notes'] ) ) {
		return;
	}

	update_post_meta( $post_id, 'notes', wp_unslash( sanitize_text_field( $POST['notes'] ) ) );
}

To see the actual code that runs checkout this repo. Pretty straight forward classic WordPress meta box registration. Everything is PHP, we create the view in our render function and when the post is saved we do some validation and update the meta value. This meta box will work just fine inside of the Gutenberg editor as well. Let's look at how we could recreate a meta box as a block in Gutenberg.

Meta block:

plugin.php

<?php
/*
 * Plugin Name: Sample Meta Block!
 * Version: 0.2.0
 * Author: Edwin Cromley
 * Author URI: https://edwincromley.com
 * License: GPL3+
 *
 * Description: A sample meta block to illustrate how easy using Gutenberg is.
 */

/**
 * Enqueue block assets.
 */
function meta_block_enqueue_block_editor_assets() {
    // Enqueue JS that registers a block.
    wp_enqueue_script(
        'meta-block',
        plugins_url( 'meta-block.js', __FILE__ ),
        // Here we declare our dependencies for creating the block.
        array( 'wp-blocks', 'wp-element', 'wp-components' )
    );
}

add_action( 'enqueue_block_editor_assets', 'meta_block_enqueue_block_editor_assets' );

/**
 * The authentication callback for our secrent notes meta key.
 *
 * @param boolean $allowed Whether the user can add the post meta. Default false.
 * @param string $meta_key The meta key.
 * @param int $post_id Post ID.
 * @param int $user_id User ID.
 * @param string $cap Capability name.
 * @param array $caps User capabilities.
 * @return boolean Whether the user has permission or not to
 */
function meta_block_secret_notes_auth_callback( $allowed, $meta_key, $post_id, $user_id, $cap, $caps ) {
    if ( current_user_can( 'edit_post', $post_id ) ) {
        return true;
    }

    return false;
}

/**
 * Register meta field for the rest API.
 */
function meta_block_init() {
    // Register a post meta
    register_meta( 'post', 'notes', array(
        'show_in_rest'  => true,
        'single'        => true,
        'auth_callback' => 'meta_block_secret_notes_auth_callback',
    ) );
}

add_action( 'init', 'meta_block_init' );

/**
 * Filters out the notes meta field from rest response for unauthenticated users.
 *
 * @param WP_REST_Response $response The response object.
 * @param WP_Post          $post     Post object.
 * @param WP_REST_Request  $request  Request object.
 * @return WP_REST_Response The modified rest response.
 */
function meta_block_make_secret_notes_secret( $response, $post, $request ) {
    $data = $response->get_data();

    if ( isset( $data['meta']['notes'] ) && ! current_user_can( 'edit_post', $post->ID ) ) {
        unset( $data['meta']['notes'] );
        $response->set_data( $data );
    }

    return $response;
}

add_filter( 'rest_prepare_post', 'meta_block_make_secret_notes_secret', 10, 3 );

/**
 * Register the block template for the post post type.
 *
 * @param array  $args      Associative array of data used during registration.
 * @param string $post_type The current post type being registered.
 * @return array Associative array of data used during registration.
 */
function meta_block_change_post_block_template( $args, $post_type ) {
    if ( $post_type === 'post' ) {
        // Create a template of our is great block.
        $secret_notes_block = array(
            'sample/secret-notes',
            array(
                'template_lock' => 'all',
            ),
        );

        // Check if template exists and if not add template.
        if ( ! isset( $args['template'] ) || ! is_array( $args['template'] ) ) {
            $args['template'] = array();
        }

        array_unshift( $args['template'], $secret_notes_block );
    }

    return $args;
}

add_filter( 'register_post_type_args', 'meta_block_change_post_block_template', 10, 2 );

meta-block.js

( function( blocks, element, components ) {
    var el       = element.createElement,
        Editable = blocks.Editable;

    blocks.registerBlockType( 'sample/secret-notes', {
        title: 'Secret Notes',
        category: 'common',
        isPrivate: true,

        /**
         * Declare the attributes involved.
         *
         * We declare the notes attribute and one of its properties is a
         * meta key, which will be the corresponding meta key we save on the
         * post meta db.
         */
        attributes: {
            notes: {
                type: 'string',
                source: 'meta',
                meta: 'notes'
            }
        },

        edit: function( props ) {
            var notes = props.attributes.notes;

            function setSecretNotes( notes ) {
                console.log( notes[0] );
                /**
                 * This is a tad bit hacky as the value passed in by the
                 * Editable component will be an array of values. In this case,
                 * it will always be an array of one string, which is the value
                 * we want.
                 */
                props.setAttributes( { notes: notes[0] } );
                event.preventDefault();
            }

            return el(
                'div',
                {
                    key: 'secret-notes',
                },
                [
                    el( 'h3', {}, 'Secret Notes:' ),
                    /**
                     * Set the value to the current value being auto loaded in.
                     * Set the onChange handler to set our new attributes, this
                     * will auto save our meta values.
                     */
                    el( Editable, { onChange: setSecretNotes, value: notes } )
                ]
            );
        },

        save: function() {
            /**
             * The save function will represent what is saved into the post's
             * post content, since we are not adding anything to the post
             * content
             */
            return null;
        }
    } );
} )(
    window.wp.blocks,
    window.wp.element,
    window.wp.components,
);

If you want to check out the code go to this repository. These are about the same lines of code, and they honestly are not much different. We define how the data should be saved and how it should be rendered in both cases. Different API, same result. So why go through all of the trouble to reimagine meta boxes as blocks?

Benefits of blocks replacing meta boxes

There are a number of benefits of blocks over anything else, however templating, uniformity, and composability are the main benefits.

Templates

If you noticed the block template being registered for the post type, this is one of the powers of using blocks instead of the classic method. You can position the meta boxes where they actually need to go. We can provide default values ahead of time as well. When you start to think about the implications for page building and themes it gets very interesting. Being able to structure your content however you want leads to much improved user experiences. The content itself can now be outlined ahead of time, which helps when many content authors are involved and need to adhere to some standard. The template system is not fully fleshed out yet, but when it is, it will be incredibly powerful. Meta boxes fall short in this aspect.

Uniformity

Sometimes meta boxes are not the correct tool for the job and we need to use another one of WordPress's APIs to get the things done. Sometimes the meta boxes I have created will power a shortcode, widget, or custom TinyMCE thing. It always gets awkward though because the experience is so disjointed.

For instance, I do a lot of eLearning projects and often a link to assignments, modules, or the current week's todo items need to be embedded in the content. These are being powered by data somewhere else often controlled by a meta box. I can't just hard code these pieces into the theme templates though, as the instructors want to have control over where these elements are placed. Meta boxes only get you part of the way to where you need to go. Then depending on the case, a TinyMCE button is created and there is a unfulfilling user experience getting this to work. If the author needs to update the list of todos, they need to find the meta box for it somewhere on another page. If they want to change the list of todos for that specific page, they most likely will need to create an entirely new list of todos. It gets tedious quickly.

Now that I am running these systems on Gutenberg, it is effortless for instructors to be onboarded and get up to speed. The block API brings us uniformity, which allows the meta box that is powering something to also be embeddable in the content, or not. It's up to us and there is incredibly flexibility. The uniformity of the experience also makes it easier for the people actually using the editor, because once they have learned how to insert, place, and update the block, they have learned it for everything else. There is no need to go to different pages, and do all sorts of weird workflows. Since blocks will unify meta boxes, content, custom TinyMCE, widgets, shortcodes, and whatever else into one thing, a new property will also arise; composability.

Composability

The new block editor does not quite fully deliver on composability yet, but it's getting there. Composability allows us to create something new out of other elements. A web page is basically a composition of various HTML tags. The HTML is parsed into DOM nodes and eventually renders onto our screen after a bunch of other stuff happens. We might create an employee profile by combining, a figure element, with an image element, a figcaption, and maybe some p elements for various information bits. If these pieces did not share a uniform interface, we could not create the employee profile in HTML, we would have to do something else. HTML would not be very useful.

WordPress's current content system for better or worse is a fragmented mess. It kind of makes sense, but as a new comer myself, it was weird learning a lot of the concepts and the WordPress way, as it seemed different for almost every piece. Widgets needed to be created one way, and could only be easily embedded in certain places, which needed to be registered beforehand, unless you wanted to hard code the widget somewhere. Confusing.

If I spent all of my effort creating a widget, well I was out of luck when I wanted to put it inside my content. Instead, I needed to then create a shortcode. If I wanted to do the same exact thing, but slightly different, I would need a new shortcode. I also wanted a widget that did the new thing as well, so I had to create another widget. If I wanted to use the new widget functionality inside the old shortcode it leads to a gigantic mess. The level of reusability is limited, and as a developer we really only have an illusion of reusability when working in WordPress.

Imagine if every time you wrote HTML, you had to keep each element as its own. For instance:

<p>Here is some HTML. Now I </p><a href="awesome.com">have a link</a><p>inside of the same text.</p>

No tag could ever be a part of another. div and its many incarnations would be unusable. We would be limited to linear ordering of basic elements, slabbing one piece after the other. If we wanted to do something complex, it would need to meticulously be strung together. Nothing could ever be grouped together or composed in a natural way. If we wanted to move a piece we would have to move everything with it. We don't work this way with HTML, so why do we accept it for how we work in WordPress. Again, imagine never using div or even conceiving what div is, that is the world we are working in. Widgets can not be used with shortcodes, or TinyMCE, and vice versa, without a lot of extra work. It is hard to fully see how limited we are by WordPress, because it is great, but when all of the pieces of content can become more easily interchangeable with each other, we will see new experiences emerge.

With the concept of composability, an image slider could potentially also be used to create any kind of slider. It could even be fashioned into a PowerPoint of sorts. The text and images, would be their own unique piece, that fit inside of a slide, and each slide inside of the slide show. Each slide could be powered by a different set of data providing a unique experience for each person. The web browser is already a PowerPoint of sorts that we don't even take into account because the fluidity between each frame is abrupt and we don't fully even know how to even effectively use the medium of the web or computers yet, at least I don't.

The block editor will make WordPress an unparalleled CMS. This is just part one of reimagining meta boxes as blocks, and I hope to show you some of the cool things I have been working on, and get you excited about the future of WordPress! The upcoming articles will be more code oriented and go over how to actually think in blocks and how to actually write the JavaScript and PHP that powers them. If you are interested by the Gutenberg editor, make sure to try it out or check in on the GitHub repository.

2 thoughts on “Rethinking Meta Boxes with the new WordPress block editor: Part 1

  1. Great post, Edwin!

    In case you didn’t get the email notification, I left a couple of comments on the GitHub repo re: your code samples.

    Also, I think your post would greatly benefit from a couple of screenshots so people know what they’re looking at.

    Thanks again!

    1. Hey Daniel,

      The future posts will definitely have more visual media.

      Thank you for the GitHub comments as well, I will patch those problems, and update the post.

      See you during the Webinar. Thank you for stopping by!

Leave a Reply

%d bloggers like this: