Block-based Widget Areas

The next phase of Gutenberg includes bringing the block editor to the Widgets screen. But we’re building websites that need this functionality right now.

I’ve come up with a simple solution for using the block editor outside of the main content area, in contexts where we used to use widget areas.

Register a Block Area post type

I create a custom post type called “Block Area”, and each unique block area is a post. To display the block area, we query for the block_area post with a specific slug and display its contents.

I built it as a class that I include in our core functionality plugin. To display the after-post block area, I add this to my theme’s single.php file:

if( function_exists( 'ea_block_area' ) )
	ea_block_area()->show( 'after-post' );

Once WordPress core adds block-based widget areas, I can migrate the block content from my block_area post type to whatever WordPress core uses, and update the show() method to use dynamic_sidebar() or a new function in WP core.

Example

I’m working on a website with an “After Post” block area for blocks that appear at the end of every article.

Code for Block Areas

I recommend including this in a core functionality plugin. You can then use the ea_block_area() function in your theme after checking if the function exists.

<?php
/**
 * Block Area
 * CPT for block-based widget areas, until WP core adds block support to widget areas
 * @link https://www.billerickson.net/wordpress-gutenberg-block-widget-areas/
 *
 * @package      CoreFunctionality
 * @author       Bill Erickson
 * @since        1.0.0
 * @license      GPL-2.0+
**/

class EA_Block_Area {

	/**
	 * Instance of the class.
	 * @var object
	 */
	private static $instance;

	/**
	 * Class Instance.
	 * @return EA_Block_Area
	 */
	public static function instance() {
		if ( ! isset( self::$instance ) && ! ( self::$instance instanceof EA_Block_Area ) ) {
			self::$instance = new EA_Block_Area();

			// Actions
			add_action( 'init',              array( self::$instance, 'register_cpt'      )    );
			add_action( 'template_redirect', array( self::$instance, 'redirect_single'   )    );
		}
		return self::$instance;
	}

	/**
	 * Register the custom post type
	 *
	 */
	function register_cpt() {

		$labels = array(
			'name'               => 'Block Areas',
			'singular_name'      => 'Block Area',
			'add_new'            => 'Add New',
			'add_new_item'       => 'Add New Block Area',
			'edit_item'          => 'Edit Block Area',
			'new_item'           => 'New Block Area',
			'view_item'          => 'View Block Area',
			'search_items'       => 'Search Block Areas',
			'not_found'          => 'No Block Areas found',
			'not_found_in_trash' => 'No Block Areas found in Trash',
			'parent_item_colon'  => 'Parent Block Area:',
			'menu_name'          => 'Block Areas',
		);

		$args = array(
			'labels'              => $labels,
			'hierarchical'        => false,
			'supports'            => array( 'title', 'editor', 'revisions' ),
			'public'              => false,
			'show_ui'             => true,
			'show_in_rest'	      => true,
			'exclude_from_search' => true,
			'has_archive'         => false,
			'query_var'           => true,
			'can_export'          => true,
			'rewrite'             => array( 'slug' => 'block-area', 'with_front' => false ),
			'menu_icon'           => 'dashicons-welcome-widgets-menus',
		);

		register_post_type( 'block_area', $args );

	}

	/**
	 * Redirect single block areas
	 *
	 */
	function redirect_single() {
		if( is_singular( 'block_area' ) ) {
			wp_redirect( home_url() );
			exit;
		}
	}

	/**
	 * Show block area
	 *
	 */
	function show( $location = '' ) {
		if( ! $location )
			return;

		$location = sanitize_key( $location );

		$loop = new WP_Query( array(
			'post_type'		=> 'block_area',
			'name'    		=> $location,
			'posts_per_page'	=> 1,
		));

		if( $loop->have_posts() ): while( $loop->have_posts() ): $loop->the_post();
			echo '<div class="block-area block-area-' . $location . '">';
				the_content();
			echo '</div>';
		endwhile; endif; wp_reset_postdata();
	}

}

/**
 * The function provides access to the class methods.
 *
 * Use this function like you would a global variable, except without needing
 * to declare the global.
 *
 * @return object
 */
function ea_block_area() {
	return EA_Block_Area::instance();
}
ea_block_area();

Bill Erickson

Bill Erickson is the co-founder and lead developer at CultivateWP, a WordPress agency focusing on high performance sites for web publishers.

About Me
Ready to upgrade your website?

I build custom WordPress websites that look great and are easy to manage.

Let's Talk

Reader Interactions

Comments are closed. Continue the conversation with me on Twitter: @billerickson

Comments

  1. Drew Poland says

    Awesome!! I’ve done something similar for awhile but this is a much cleaner version.

    • Bill Erickson says

      Very cool! I haven’t seen that before. That seems to be a great way to get blocks into existing widget areas.

  2. Joris Kroos says

    Pretty neat function;

    The code “$page = get_the_ID() ? get_the_ID() : $_POST[‘post_id’];”

    Which you advised on the header block, doesn’t work combined with the block area’s, returning the ID of the block area instead.

    Seems the block area’s won’t do any good when trying to fetch content based on acf values of the main post. How does your Display Posts shortcode solve this?

    • Bill Erickson says

      Use `get_queried_object_id()` instead, which returns the ID of the queried object (ex: post ID when viewing a single post) regardless of any custom loops you may be in currently.

  3. Victor Ramirez says

    How are you handling user registration & creation of block areas?

    I like that you create a CPT similar to your category landing pages plugin. Great for admin use.

    If someone hits “add new” how are they assigning it to a specific area? Or are you only recognizing ‘block areas’ that you create and restricting users from ‘add new’?

    • Bill Erickson says

      I’m not preventing users from creating new block areas, although it probably would be a good idea because user-generated block areas won’t appear anywhere on the site.

  4. David Smyth says

    This is awesome thank you. I recommend tacking on a shortcode so this can be easily added in a sidebar widget, simply:

    add_shortcode(‘show-block-area’, function ($atts, $content = null) {
    $atts = shortcode_atts(array(
    ‘block’ => ”,
    ), $atts);

    ea_block_area()->show($atts[‘block’]);
    });

    • Bill Erickson says

      That’s an interesting way to use this functionality.

      Personally I use the block area as a replacement for the widget area, so I’ll replace the dynamic_sidebar( 'sidebar' ); with ea_block_area()->show( 'sidebar' );. But you could definitely use the block area inside the widget area with a shortcode.

  5. Daniel Post says

    Hi Bill,

    Thanks for sharing! Definitely an interesting approach to solving this problem. How do you create new block areas? It appears that you’re rendering them in code only, so end users wouldn’t be able to create new ones and then show them somewhere, correct? Or do you also have a block that renders a block area?

    Little side note: wouldn’t [‘publicly_queryable’ => false] on the CPT negate the need for the redirect?

    • Bill Erickson says

      In most cases we’re using them for hard-coded template areas, like Sidebar and Before Footer.

      There are a few instances where we let clients create their own. I’m building a site right now that has an After Post block area which includes things like Email CTA and related posts. The client can create category-specific ones by first creating the block area, then editing the category and selecting which block area to use.

      When we load the post, we check the current category for a category-specific block area, and if not set we use the global default “After Post” one.

      Beyond this, I haven’t found a need for clients creating block areas and inserting them on their own. If it’s a repeatable piece of content in the main content area, they can use Reusable Blocks for it. We use these block areas for theme locations outside the main content area.

  6. Daniel Post says

    Apologies for the double comment. Thinking about this some more: a cool approach would be to let the theme/plugin define which block areas are available, similar to navigation menus. When editing a block area, you could then pick one or more areas where those specific blocks will show up.

    • Bill Erickson says

      That’s a nice idea! I’ve held off on expanding this functionality too much because most of it is coming to WordPress core.

      WordPress 5.6 is converting widget areas to block areas, so a theme can define the available block areas by registering widget areas.

  7. Leo says

    Hi Bill,

    I’m very new to everything you’re doing. So excuse me if this I’m asking something silly!

    If I understand this post correctly, you created a way to use saved gutenberg blocks in widgets area’s, or as you call them: block area’s?

    I am using Genesis. Could I also use a Genesis Block in one of their preset locations, such as after_header?

    And could I also make that area show for example only on blog pages, or on the homepage?

    • Bill Erickson says

      This approach doesn’t technically let you use blocks in widget areas. It created a new post type called “Block Areas”, and you use those block areas as a replacement for your widget areas.

      Here’s a screenshot of what it looks like editing a sidebar on a recent project: https://a.cl.ly/YEuQm8lX

        • Bill Erickson says

          I use the `admin_body_class` filter to add a unique class to the body in the WP editor. For standard posts and pages I add whatever page layout we’re using (ex: content-sidebar, full-width-content). For block areas, I usually add the post slug (with a prefix), so if the block area is named “Sidebar” it will have a body class of .be-sidebar in the admin.

          I then use the `enqueue_block_editor_assets` hook to load a CSS file into the editor that then styles the editor using that class. Ex: .be-sidebar .editor-styles-wrapper .wp-block { max-width: 300px; }