Extending WordPress Block Editor

Summary: Learning about extending the WordPress Block Editor UI.

Extending The Editor UI

The WordPress Block Editor introduced a whole new way of creating content with a block-based architecture. Developers have the power to extend the editor’s functionality by creating custom blocks, modifying existing ones, or adding additional tools and UI enhancements. This guide delves deep into extending the Gutenberg editor and building custom blocks, covering everything from setting up the development environment to advanced features such as block attributes, custom styles, and dynamic blocks.

  • Blocks: The basic unit of content in Gutenberg. Each block can represent anything from a paragraph to a complex media layout.
  • Editor: The user interface where blocks are added, rearranged, and configured. It’s powered by React and includes various components, such as the block inserter, inspector controls, toolbar, and more.
  • Block Types: There are different types of blocks, including:
  • Static Blocks: These render the same HTML on both the front-end and back-end.
  • Dynamic Blocks: These use PHP to generate dynamic content during rendering, based on user input or other criteria.
  • InnerBlocks: Special blocks that can contain other blocks, like the group or columns block.
  • Declarative Structure: Blocks are declared with their attributes and behavior. React is heavily used in both the admin side (Block Editor) and front-end.
  • Modularity: Blocks are modular and reusable components.
  • Hooks: Gutenberg makes extensive use of JavaScript hooks, such as useSelect, useDispatch, and useState, which play crucial roles in retrieving and manipulating data.
  • Attributes: Attributes are the block’s data points, which can be modified by the user and saved to the block’s JSON structure.

Block Attributes

Attributes define the editable data for blocks. They can be of various types: strings, booleans, numbers, or even complex structures like arrays and objects.

      attributes: {
          content: {
              type: 'string',
              source: 'html',
              selector: 'p',
          },
          textColor: {
              type: 'string',
              default: '#000',
          },
      }
    

    Block Categories and Icons

    When registering a block, we can assign it to one of the default categories (common, formatting, widgets, etc.) or create our own category.

        function my_custom_block_category( $categories, $post ) {
            return array_merge(
                $categories,
                array(
                    array(
                        'slug' => 'my-category',
                        'title' => __( 'My Custom Category', 'my-plugin' ),
                    ),
                )
            );
        }
        add_filter( 'block_categories', 'my_custom_block_category', 10, 2 );
      

      InnerBlocks

      The InnerBlocks component allows us to create a block that contains other blocks. This is useful for container blocks like Group or Columns.

          const { InnerBlocks } = wp.blockEditor;
        
          registerBlockType('custom-blocks/container-block', {
              title: 'Container Block',
              category: 'layout',
              edit() {
                  return <InnerBlocks />;
              },
              save() {
                  return <InnerBlocks.Content />;
              },
          });
        

        Block Controls (Toolbar & Inspector)

        Gutenberg provides components like BlockControls and InspectorControls to add custom controls to our blocks.

            const { BlockControls } = wp.blockEditor;
            const { ToolbarGroup, ToolbarButton } = wp.components;
          
            <BlockControls>
                <ToolbarGroup>
                    <ToolbarButton
                        icon="admin-site"
                        label="Custom Button"
                        onClick={() => console.log('Clicked')}
                    />
                </ToolbarGroup>
            </BlockControls>
          
              const { InspectorControls } = wp.blockEditor;
              const { PanelBody, ColorPicker } = wp.components;
            
              <InspectorControls>
                  <PanelBody title="Text Color">
                      <ColorPicker
                          color={attributes.textColor}
                          onChange={(color) => setAttributes({ textColor: color })}
                      />
                  </PanelBody>
              </InspectorControls>
            

            Dynamic Blocks

            Dynamic blocks render their content on the server-side using PHP. This allows us to output dynamic content like recent posts or custom post types.

                function render_dynamic_block( $attributes ) {
                    $recent_posts = wp_get_recent_posts( array(
                        'numberposts' => 1,
                        'post_status' => 'publish',
                    ) );
              
                    if ( count( $recent_posts ) === 0 ) {
                        return 'No posts';
                    }
              
                    $post = $recent_posts[0];
                    return sprintf(
                        '<a href="%1$s">%2$s</a>',
                        esc_url( get_permalink( $post['ID'] ) ),
                        esc_html( $post['post_title'] )
                    );
                }
              
                register_block_type( 'custom-blocks/recent-posts', array(
                    'render_callback' => 'render_dynamic_block',
                ) );
              

              Adding Custom Controls

              Once an attribute is added, we may also need to add custom controls for users to edit the attribute’s value.

                  const { addFilter } = wp.hooks;
                  const { InspectorControls } = wp.blockEditor;
                  const { PanelBody, TextControl } = wp.components;
                
                  function addParagraphControls( BlockEdit ) {
                      return ( props ) => {
                          if ( props.name !== 'core/paragraph' ) {
                              return <BlockEdit { ...props } />;
                          }
                
                          return (
                              <>
                                  <BlockEdit { ...props } />
                                  <InspectorControls>
                                      <PanelBody title="Custom Class">
                                          <TextControl
                                              label="Custom CSS Class"
                                              value={props.attributes.customClass}
                                              onChange={(value) =>
                                                  props.setAttributes({ customClass: value })
                                              }
                                          />
                                      </PanelBody>
                                  </InspectorControls>
                              </>
                          );
                      };
                  }
                
                  addFilter(
                      'editor.BlockEdit',
                      'custom-blocks/add-paragraph-controls',
                      addParagraphControls
                  );
                

                Extensibility APIs

                Gutenberg provides several APIs that we can leverage to extend its functionality.

                Block API

                The Block API is the most fundamental API that allows us to register and manipulate blocks.

                    wp.blocks.registerBlockType('namespace/block-name', {
                        // Block settings here
                    });
                  

                  Data API

                  The Data API allows developers to interact with the WordPress state, retrieve data, and dispatch actions.

                      const { useSelect } = wp.data;
                    
                      const selectedPost = useSelect((select) =>
                          select('core/editor').getCurrentPost()
                      );
                    

                    Components API

                    Gutenberg provides a wide array of reusable React components, such as Button, PanelBody, and TextControl. These can be used to create rich UI elements in custom blocks and extensions.

                        const { Button } = wp.components;
                      
                        <Button isPrimary onClick={handleClick}>Click Me</Button>;
                      

                      SlotFills

                      The SlotFill API in Gutenberg (the WordPress block editor) provides a powerful way to extend the editor’s UI by allowing developers to “inject” custom components into predefined locations (slots) within the editor interface. By using this API, we can add custom sidebars, toolbars, panels, or controls to enhance the block editor experience without modifying core components.

                      SlotFill is a React-based API used in Gutenberg that allows developers to extend the editor interface by placing custom components into predefined slots. These slots are scattered throughout the editor interface, including toolbars, sidebars, menus, and settings panels. The developer can define a “fill” that is injected into the “slot” when necessary.

                      The Slot acts as a placeholder, and the Fill is the custom UI component that fills that placeholder when rendered.

                      • Slot: Represents a predefined location in the UI.
                      • Fill: Represents the custom UI elements we want to place in a slot.
                      • Dynamic UI Extensions: SlotFill allows dynamic insertion of components without directly altering the core code.
                      • Slots and Fills: Slots are predefined areas of the UI (e.g., sidebars, inspector panels, and toolbars), and Fills are what developers inject into those slots.
                      • Reactivity: SlotFill is built on React, allowing us to update and interact with the editor dynamically.

                      SlotFill API Components

                      • Slot: This is a component provided by WordPress where our custom UI will appear. The Slot is defined in the core editor UI and is what we are targeting to extend.
                      • Fill: This is a component that we create and insert into the Slot. The Fill is our custom content or functionality.
                      • PluginSidebar and PluginSidebarMoreMenuItem: These are specific types of Slots and Fills used to add items to the editor’s sidebar and its more menu, commonly used in extending editor functionality.

                      Available Slots

                      There are several predefined slots in the block editor where we can inject custom functionality. Some commonly used slots include:

                      • PluginSidebar: Used to add custom sidebars to the editor.
                      • PluginMoreMenuItem: Adds custom items to the editor’s more menu (the three-dots menu).
                      • BlockControls: Adds items to the block toolbar.
                      • InspectorControls: Adds controls to the settings panel of a block.
                      • ToolbarButton: Adds buttons to the block toolbar or other toolbars.

                      Adding a Custom Sidebar Using PluginSidebar

                      One of the most common uses of the SlotFill API is to add a custom sidebar to the block editor. This sidebar can contain custom settings, controls, or any UI components we want to provide for our blocks or editor.

                         <?php
                         function custom_slotfill_enqueue_assets() {
                             wp_enqueue_script(
                                 'custom-slotfill-script',
                                 plugins_url( 'slotfill.js', __FILE__ ),
                                 array( 'wp-plugins', 'wp-edit-post', 'wp-element', 'wp-components', 'wp-data' ),
                                 filemtime( plugin_dir_path( __FILE__ ) . 'slotfill.js' )
                             );
                         }
                         add_action( 'enqueue_block_editor_assets', 'custom_slotfill_enqueue_assets' );
                      
                         const { PluginSidebar, PluginSidebarMoreMenuItem } = wp.editPost;
                         const { PanelBody, TextControl } = wp.components;
                         const { registerPlugin } = wp.plugins;
                         const { Fragment } = wp.element;
                         const { useSelect, useDispatch } = wp.data;
                      
                         const MyCustomSidebar = () => {
                             // Example state management using Gutenberg's data API
                             const postTitle = useSelect( ( select ) => select( 'core/editor' ).getEditedPostAttribute( 'title' ) );
                             const { editPost } = useDispatch( 'core/editor' );
                      
                             return (
                                 <PluginSidebar
                                     name="my-custom-sidebar"
                                     title="Custom Sidebar"
                                     icon="admin-site">
                                     <PanelBody title="Post Settings">
                                         <TextControl
                                             label="Post Title"
                                             value={ postTitle }
                                             onChange={ ( title ) => editPost( { title } ) }
                                         />
                                     </PanelBody>
                                 </PluginSidebar>
                             );
                         };
                      
                         // Register Plugin
                         registerPlugin( 'my-custom-sidebar', {
                             render: MyCustomSidebar,
                             icon: 'admin-site',
                         });
                      
                         const MyCustomSidebar = () => (
                             <Fragment>
                                 <PluginSidebarMoreMenuItem
                                     target="my-custom-sidebar">
                                     Custom Sidebar
                                 </PluginSidebarMoreMenuItem>
                                 <PluginSidebar
                                     name="my-custom-sidebar"
                                     title="Custom Sidebar"
                                     icon="admin-site">
                                     <PanelBody title="Post Settings">
                                         {/* Custom controls go here */}
                                     </PanelBody>
                                 </PluginSidebar>
                             </Fragment>
                         );
                      
                         registerPlugin( 'my-custom-sidebar', { render: MyCustomSidebar } );
                      

                      Adding a Toolbar Button Using BlockControls

                      Another powerful use of SlotFill is to add custom buttons or controls to block toolbars using the BlockControls slot. This is useful when we want to add block-specific actions.

                      const { registerPlugin } = wp.plugins;
                      const { BlockControls } = wp.blockEditor;
                      const { ToolbarGroup, ToolbarButton } = wp.components;
                      const { Fragment } = wp.element;
                      
                      const CustomToolbarButton = () => {
                          return (
                              <Fragment>
                                  <BlockControls>
                                      <ToolbarGroup>
                                          <ToolbarButton
                                              icon="admin-site"
                                              label="My Custom Button"
                                              onClick={ () => alert( 'Button clicked!' ) }
                                          />
                                      </ToolbarGroup>
                                  </BlockControls>
                              </Fragment>
                          );
                      };
                      
                      registerPlugin( 'my-custom-toolbar-button', {
                          render: CustomToolbarButton,
                      } );
                      

                      Extending Inspector Controls Using InspectorControls

                      The InspectorControls slot allows us to add custom settings to a block’s inspector panel (the settings panel that appears on the right side of the editor).

                      const { InspectorControls } = wp.blockEditor;
                      const { PanelBody, ToggleControl } = wp.components;
                      const { Fragment } = wp.element;
                      
                      const CustomInspectorControl = ( props ) => {
                          const { attributes, setAttributes } = props;
                      
                          return (
                              <Fragment>
                                  <InspectorControls>
                                      <PanelBody title="Custom Settings">
                                          <ToggleControl
                                              label="Enable Feature"
                                              checked={ attributes.customFeature }
                                              onChange={ (value) => setAttributes( { customFeature: value } ) }
                                          />
                                      </PanelBody>
                                  </InspectorControls>
                              </Fragment>
                          );
                      };
                      
                      wp.blocks.registerBlockType( 'my-plugin/my-block', {
                          title: 'My Block',
                          category: 'common',
                          attributes: {
                              customFeature: {
                                  type: 'boolean',
                                  default: false,
                              },
                          },
                          edit: CustomInspectorControl,
                          save: () => {
                              // Save logic
                          },
                      } );
                      
                      «
                      »

                      Leave a Reply

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