Creating an Admin UI for Block Patterns in Gutenberg

Dev note as of 8/21/23:
With the addition of “synced patterns” in WordPress 6.3, this method is no longer needed.

In this post I will discus how I created a custom post type to create and manage block patterns directly within the WordPress dashboard.

I’ve been a big proponent of reusable blocks for the simple fact that they can be created and manged within the WordPress admin with no coding skills. This makes it very simple for clients/end users to create/edit/manage their own reusable blocks.

The main complaint from clients is that they forget to convert the reusable blocks to standard blocks when they add a reusable block to a page, which can cause all kinds of editing issues.

Enter block patterns. Block patterns are essentially the same thing, but once added to a page they are completely decoupled from the pattern. No “converting to blocks” to remember. The problem with block patterns is that there is no user-facing UI to create, edit or manage them. They need to be setup with code which entails a number of steps that are pretty tedious and time consuming (see Rich Tabor’s post on that).

Note: All of the code below is in my own core functionality plugin. You are welcome to copy/fork as needed. My Core Functionality plugin can be found on GitHub here. The full code will be shown at the bottom of the post.

Note 2: This post assuming you are using Advanced Custom Fields for metaboxes. If you aren’t, you would need to adjust how you output the categories metabox.

Step 1: Set up the Block Pattern Custom Post Type

<?php
/*
* Register Block Patterns Custom Post Type
*/
add_action( 'init', 'wd_register_cpt_block_pattern' );
function wd_register_cpt_block_pattern() {

     $labels = [
          'name'               => _x( 'Block Pattern', 'post type general name', WD_PLUGIN_THEME_NAME ),
          'singular_name'      => _x( 'Block Pattern', 'post type singular name', WD_PLUGIN_THEME_NAME ),
          'menu_name'          => _x( 'Block Patterns', 'admin menu', WD_PLUGIN_THEME_NAME ),
          'name_admin_bar'     => _x( 'Block Pattern', 'add new on admin bar', WD_PLUGIN_THEME_NAME ),
          'add_new'            => _x( 'Add New', 'Block Pattern', WD_PLUGIN_THEME_NAME ),
          'add_new_item'       => __( 'Add New Block Pattern', WD_PLUGIN_THEME_NAME ),
          'new_item'           => __( 'New Block Pattern', WD_PLUGIN_THEME_NAME ),
          'edit_item'          => __( 'Edit Block Pattern', WD_PLUGIN_THEME_NAME ),
          'view_item'          => __( 'View Block Pattern', WD_PLUGIN_THEME_NAME ),
          'all_items'          => __( 'All Block Patterns', WD_PLUGIN_THEME_NAME ),
          'search_items'       => __( 'Search Block Patterns', WD_PLUGIN_THEME_NAME ),
          'parent_item_colon'  => __( 'Parent Block Patterns:', WD_PLUGIN_THEME_NAME ),
          'not_found'          => __( 'No Block Patterns found.', WD_PLUGIN_THEME_NAME ),
          'not_found_in_trash' => __( 'No Block Patterns found in Trash.', WD_PLUGIN_THEME_NAME )
     ];

     $args = [
          'labels'              => $labels,
          'description'         => __( 'Block Patterns.', WD_PLUGIN_THEME_NAME ),
          'public'              => false,
          'exclude_from_search' => true,
          'publicly_queryable'  => true,
          'show_ui'             => true,
          'show_in_menu'        => true,
          'query_var'           => true,
          'capability_type'     => 'post',
          'has_archive'         => false,
          'hierarchical'        => false,
          'menu_position'       => 28,
          'menu_icon'           => 'dashicons-align-left',
          'show_in_rest'        => true,
          'supports'            => [
               'title',
               'editor',
               'revisions',
          ],
          'rewrite' => [
               'slug'       => 'block-pattern',
               'with_front' => false,
          ],
     ];

     register_post_type(
          'block_pattern',
          $args,
     );
}

/*
* Redirect single posts to home page
*/
add_action( 'template_redirect', 'wd_block_patterns_cpt_redirect_single' );
function wd_block_patterns_cpt_redirect_single() {

     if ( is_singular( 'block_pattern' ) ) {
          wp_redirect( get_home_url() );
          exit;
     }
}

The first function above is wd_register_cpt_block_pattern. This function registers the custom block_pattern post type. Notice we setting public and has_archive to false to ensure it doesn’t output on the front end.

The second function is wd_block_patterns_cpt_redirect_single which simply redirects all of the single pages that are created back to the home page to ensure if anyone happens to try to access these posts directly, it will just redirect them to the home page.

Now we have a working Block Patterns Custom Post Type that is not accessible on the front end of the site.

Step 2: Optionally remove the Core Block Patterns

<?php
/**
* Remove core block patterns (optional)
*
* Optionally disable core block patterns
*/
// remove_theme_support( 'core-block-patterns' );

The function above will remove the block patterns that come packaged with WordPress. Simply un-comment the remove_theme_support line to disable the core block patterns.

Step 3: Set up the Block Pattern Categories

<?php
/**
* Add custom block pattern categories
*/
function wd_register_block_pattern_categories() {

     if ( class_exists( 'WP_Block_Patterns_Registry' ) ) {

          register_block_pattern_category(
               'whiteley-designs',
               [
                    'label' => _x( 'Whiteley Designs', 'Block pattern category', WD_CHILD_THEME_NAME )
               ]
          );

     }

}
add_action( 'init', 'wd_register_block_pattern_categories' );

Now that we have our Block Patterns custom post type we will setup the block pattern categories. The first function in the snippet above is wd_register_block_pattern_categories. This function is to register any custom categories you would like to add. If you don’t want any custom categories, you can simply ignore this function.

<?php
/**
* Create categories metabox
*
* @link https://www.advancedcustomfields.com/resources/register-fields-via-php/
*/
add_action( 'acf/init', 'wd_add_block_patterns_acf_metabox' );
function wd_add_block_patterns_acf_metabox() {

     // set array of categories to offer
     $block_pattern_categories = [
          'buttons'          => 'Buttons', // core
          'columns'          => 'Columns', // core
          'gallery'          => 'Gallery', // core
          'header'           => 'Headers', // core
          'text'             => 'Text', // core
          'query'            => 'Query', // core
          'whiteley-designs' => 'Whiteley Designs', // custom
     ];

     // register the ACF field group
     acf_add_local_field_group(
          [
               'key'    => 'group_61a8f5eb8dc40',
               'title'  => 'Block Pattern Categories',
               'fields' => [
                    [
                         'key'               => 'field_61a8f608a61fa',
                         'label'             => 'Block Pattern Categories',
                         'name'              => 'wd_block_pattern_categories',
                         'type'              => 'checkbox',
                         'instructions'      => '',
                         'required'          => 0,
                         'conditional_logic' => 0,
                         'choices'           => $block_pattern_categories,
                         'allow_custom'      => 0,
                         'default_value'     => [],
                         'layout'            => 'vertical',
                         'toggle'            => 0,
                         'return_format'     => 'value',
                         'save_custom'       => 0,
                         'wrapper' => [
                              'width' => '',
                              'class' => '',
                              'id'    => '',
                         ],
                    ]
               ],
               'location' => [
                    [
                         [
                              'param'    => 'post_type',
                              'operator' => '==',
                              'value'    => 'block_pattern',
                         ]
                    ]
               ],
               'menu_order' => 0,
               'position'   => 'side',
               'style'      => 'default',
               'label_placement' => 'top',
               'instruction_placement' => 'label',
               'hide_on_screen'        => '',
               'active'                => true,
               'description'           => '',
               'show_in_rest'          => 0,
          ]
     );
}

The next function is wd_add_block_patterns_acf_metabox. This function does a few things.

First, we set an array of available block pattern categories. You can include any of the core categories you want to use (buttons, columns, gallery, headers, text, query) as well as any custom ones that were added in the wd_register_block_pattern_categories function.

Second, it uses the acf_add_local_field_group to register an ACF field group for a checkbox field. We use the $block_pattern_categories array we just created to populations for the checkbox and set it up to show on the block_pattern custom post type. We now have a checkbox list of categories you can assign the block patterns to.

Step 4: Register the Block Patterns

<?php
/*
* Register Block Patterns
*/
add_action( 'init', 'wd_register_block_patterns' );
function wd_register_block_patterns() {

     if ( class_exists( 'WP_Block_Patterns_Registry' ) ) {

          $loop = new WP_Query(
               [
                    'post_type'      => 'block_pattern',
                    'posts_per_page' => -1,
               ]
          );

          if ( $loop->have_posts() ) : while ( $loop->have_posts() ) : $loop->the_post();

               $post_title = get_the_title();
               $post_slug = str_replace( ' ', '-', strtolower( $post_title ) );
               $post_content = get_the_content();
               $block_pattern_categories = get_field( 'wd_block_pattern_categories', get_the_ID() );

               register_block_pattern(
                    'wd/' . $post_slug,
                    [
                         'title'      => $post_title,
                         'content'    => trim( $post_content ),
                         'categories' => $block_pattern_categories,
                         'keywords'   => [
                              $post_title,
                              'pattern',
                              'block pattern',
                         ]
                    ]
               );

          endwhile; endif; wp_reset_postdata();

     }
}

Now that the CPT is setup and our categories are in place we are ready to register the block patterns. The above snippet will loop over any pages created in the new Block Patterns custom post type and register it at a block pattern.

Here is how it works:

  • Set the Post Title as the title of the block pattern
  • Set the content of the block pattern from the content of the post
  • Set the categories of the post from the ACF block pattern category field
  • Set the post title as a search keyword

Let’s see it in action!

Summary / Full Code

To summarize, we have on the following:

  • Registered a custom block patterns post type
  • Redirected all single posts from the block patterns custom post type to the home page
  • Set up custom block pattern categories
  • Optionally removed core block patterns
  • Registered an ACF field group to allow selection of block pattern categories
  • Registered all pages in custom block patterns post type as block patterns

Here is the full code snippet:

<?php
/**
* Setup front end UI for Block Patterns
*
* @author       WhiteleyDesigns
* @since        1.0.0
* @license      GPL-2.0+
*
*/

/*
* Register Block Patterns Custom Post Type
*/
add_action( 'init', 'wd_register_cpt_block_pattern' );
function wd_register_cpt_block_pattern() {

     $labels = [
          'name'               => _x( 'Block Pattern', 'post type general name', WD_PLUGIN_THEME_NAME ),
          'singular_name'      => _x( 'Block Pattern', 'post type singular name', WD_PLUGIN_THEME_NAME ),
          'menu_name'          => _x( 'Block Patterns', 'admin menu', WD_PLUGIN_THEME_NAME ),
          'name_admin_bar'     => _x( 'Block Pattern', 'add new on admin bar', WD_PLUGIN_THEME_NAME ),
          'add_new'            => _x( 'Add New', 'Block Pattern', WD_PLUGIN_THEME_NAME ),
          'add_new_item'       => __( 'Add New Block Pattern', WD_PLUGIN_THEME_NAME ),
          'new_item'           => __( 'New Block Pattern', WD_PLUGIN_THEME_NAME ),
          'edit_item'          => __( 'Edit Block Pattern', WD_PLUGIN_THEME_NAME ),
          'view_item'          => __( 'View Block Pattern', WD_PLUGIN_THEME_NAME ),
          'all_items'          => __( 'All Block Patterns', WD_PLUGIN_THEME_NAME ),
          'search_items'       => __( 'Search Block Patterns', WD_PLUGIN_THEME_NAME ),
          'parent_item_colon'  => __( 'Parent Block Patterns:', WD_PLUGIN_THEME_NAME ),
          'not_found'          => __( 'No Block Patterns found.', WD_PLUGIN_THEME_NAME ),
          'not_found_in_trash' => __( 'No Block Patterns found in Trash.', WD_PLUGIN_THEME_NAME )
     ];

     $args = [
          'labels'              => $labels,
          'description'         => __( 'News.', WD_PLUGIN_THEME_NAME ),
          'public'              => false,
          'exclude_from_search' => true,
          'publicly_queryable'  => true,
          'show_ui'             => true,
          'show_in_menu'        => true,
          'query_var'           => true,
          'capability_type'     => 'post',
          'has_archive'         => false,
          'hierarchical'        => false,
          'menu_position'       => 28,
          'menu_icon'           => 'dashicons-align-left',
          'show_in_rest'        => true,
          'supports'            => [
               'title',
               'editor',
               'revisions',
          ],
          'rewrite' => [
               'slug'       => 'block-pattern',
               'with_front' => false,
          ],
     ];

     register_post_type(
          'block_pattern',
          $args,
     );
}


/**
* Add custom block pattern categories
*/
function wd_register_block_pattern_categories() {

     if ( class_exists( 'WP_Block_Patterns_Registry' ) ) {

          register_block_pattern_category(
               'whiteley-designs',
               [
                    'label' => _x( 'Whiteley Designs', 'Block pattern category', WD_CHILD_THEME_NAME )
               ]
          );

     }

}
add_action( 'init', 'wd_register_block_pattern_categories' );


/**
* Remove core block patterns (optional)
*
* Optionally disable core block patterns
*/
// remove_theme_support( 'core-block-patterns' );


/**
* Create categories metabox
*
* @link https://www.advancedcustomfields.com/resources/register-fields-via-php/
*/
add_action( 'acf/init', 'wd_add_block_patterns_acf_metabox' );
function wd_add_block_patterns_acf_metabox() {

     // set array of categories to offer
     $block_pattern_categories = [
          'buttons'          => 'Buttons', // core
          'columns'          => 'Columns', // core
          'gallery'          => 'Gallery', // core
          'header'           => 'Headers', // core
          'text'             => 'Text', // core
          'query'            => 'Query', // core
          'whiteley-designs' => 'Whiteley Designs', // custom
     ];

     // register the ACF field group
     acf_add_local_field_group(
          [
               'key'    => 'group_61a8f5eb8dc40',
               'title'  => 'Block Pattern Categories',
               'fields' => [
                    [
                         'key'               => 'field_61a8f608a61fa',
                         'label'             => 'Block Pattern Categories',
                         'name'              => 'wd_block_pattern_categories',
                         'type'              => 'checkbox',
                         'instructions'      => '',
                         'required'          => 0,
                         'conditional_logic' => 0,
                         'choices'           => $block_pattern_categories,
                         'allow_custom'      => 0,
                         'default_value'     => [],
                         'layout'            => 'vertical',
                         'toggle'            => 0,
                         'return_format'     => 'value',
                         'save_custom'       => 0,
                         'wrapper' => [
                              'width' => '',
                              'class' => '',
                              'id'    => '',
                         ],
                    ]
               ],
               'location' => [
                    [
                         [
                              'param'    => 'post_type',
                              'operator' => '==',
                              'value'    => 'block_pattern',
                         ]
                    ]
               ],
               'menu_order' => 0,
               'position'   => 'side',
               'style'      => 'default',
               'label_placement' => 'top',
               'instruction_placement' => 'label',
               'hide_on_screen'        => '',
               'active'                => true,
               'description'           => '',
               'show_in_rest'          => 0,
          ]
     );
}


/*
* Redirect single posts to home page
*/
add_action( 'template_redirect', 'wd_block_patterns_cpt_redirect_single' );
function wd_block_patterns_cpt_redirect_single() {

     // redirect single and archive pages for "tutorials" custom post type to support library
     if ( is_singular( 'block_pattern' ) ) {
          wp_redirect( get_home_url() );
          exit;
     }
}


/*
* Register Block Patterns
*/
add_action( 'init', 'wd_register_block_patterns' );
function wd_register_block_patterns() {

     if ( class_exists( 'WP_Block_Patterns_Registry' ) ) {

          $loop = new WP_Query(
               [
                    'post_type'      => 'block_pattern',
                    'posts_per_page' => -1,		  
               ]
          );

          if ( $loop->have_posts() ) : while ( $loop->have_posts() ) : $loop->the_post();

               $post_title = get_the_title();
               $post_slug = str_replace( ' ', '-', strtolower( $post_title ) );
               $post_content = get_the_content();
               $block_pattern_categories = get_field( 'wd_block_pattern_categories', get_the_ID() );

               register_block_pattern(
                    'wd/' . $post_slug,
                    [
                         'title' => $post_title,
                         'content' => trim( $post_content ),
                         'categories' => $block_pattern_categories,
                         'keywords' => [
                              $post_title,
                              'pattern',
                              'block pattern',
                         ]
                    ]
               );

          endwhile; endif; wp_reset_postdata();

     }
}

Final note: Make sure you always have at least one category available. If you remove theme support for core blocks and there are no available categories the editor will crash when you try to add a block pattern.

Leave a Reply

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