Step-by-Step Guide to Building a WordPress Gutenberg Block Plugin from Scratch by AJ Partners Ltd. and Abel Rogers

Step-by-Step Guide to Building a WordPress Gutenberg Block Plugin from Scratch

Introduction

Welcome to this comprehensive guide on creating your very own WordPress Gutenberg block plugin.

If you’ve been involved with WordPress in recent years, you’ve undoubtedly heard of Gutenberg. Introduced in WordPress 5.0, Gutenberg revolutionized the way we create content on the platform. Moving away from the classic TinyMCE editor, Gutenberg introduced a block-based system, enabling users to add, edit, and style individual blocks to build their content. This new editor simplifies website creation and makes it more accessible to people without coding knowledge.

However, Gutenberg’s impact extends far beyond user experience – it also opened up exciting possibilities for developers. With Gutenberg, developers can create custom blocks, providing users with even more options for customization. Whether it’s a unique call-to-action button, a custom image slider, or a complex layout, if you can imagine it, you can build it as a Gutenberg block.

That’s where this guide comes in. In this article, we’ll walk you through the process of building a Gutenberg block plugin, starting from the very basics. This tutorial is ideal for anyone interested in WordPress development, whether you’re a seasoned developer or a beginner just getting started. By the end of this guide, you’ll have a working Gutenberg block that changes shapes and colors randomly – a fun and practical way to learn about block development.

Before we start, it’s worth mentioning that this tutorial assumes a basic understanding of JavaScript and React, as these are the primary technologies Gutenberg blocks are built with. But even if you’re not a React expert, don’t worry. We’ll explain each step in detail, helping you understand the process and learn as you go.

So, are you ready to dive into the world of Gutenberg block development? Let’s get started!

Setting Up Your Development Environment

Before we can start coding our Gutenberg block, we need to set up a development environment. This environment should include a local WordPress installation where we can test our block, as well as Node.js and npm (Node Package Manager) to help us manage our JavaScript dependencies.

Local WordPress Installation

There are several ways to install WordPress locally on your computer, but some of the easiest methods involve using tools like Local by Flywheel, MAMP, or XAMPP. These tools provide an all-in-one local development solution, allowing you to create a new WordPress site with just a few clicks. Choose one that suits your operating system and follow their installation guide to set up a local WordPress site. I won’t go into the details of installation and wordpress setup as assume that you can do that.

Installing Node.js and npm

node.js and npm are essential parts to building a WordPress Gutenberg block

Next, we need to install Node.js and npm. Node.js is a JavaScript runtime that lets you run JavaScript on your server or your machine, while npm is a package manager for Node.js.

You can download Node.js and npm from the official Node.js website. The Node.js installer includes npm, so you don’t need to install it separately. We recommend installing the LTS (Long Term Support) version as it is the most stable.

After installing, open a new terminal window and verify that Node.js and npm are correctly installed by running the following commands:

node -v
npm -v

These commands should display the versions of Node.js and npm installed on your system, respectively.

Installing @wordpress/create-block

Now that we have Node.js and npm set up, we can install the @wordpress/create-block package. This package, provided by WordPress, is a scaffolding tool that helps you get started with block development quickly. It sets up a development environment tailored for WordPress and pre-configures tools like webpack and Babel for you.

To install @wordpress/create-block, run the following command in your terminal:

npm install -g @wordpress/create-block

The -g flag installs the package globally, making it accessible from anywhere on your system.

Creating Your First Gutenberg Block

Now that our development environment is ready, let’s start creating our custom Gutenberg block. We’ll use @wordpress/create-block to create a block with default configurations.

Generating the Block

Open your terminal and navigate to the plugins directory of your local WordPress installation. For example, if you’re using Local by Flywheel, your directory path may look like this:

cd ~/Local Sites/testsite/app/public/wp-content/plugins/

Once you’re in the plugins directory, run the following command:

npx @wordpress/create-block abel-display

Replace abel-display with the name you want for your plugin. This command will create a new directory with the same name as your plugin. It will also generate boilerplate files and configurations necessary for your Gutenberg block.

Exploring the Generated Block

Navigate into the new plugin directory:

cd abel-display

Here, you’ll see several files and directories. The most important ones are:

  • src: This directory contains the source files for your block, including edit.js (which controls how your block appears in the editor) and save.js (which controls how your block appears on the front end).
  • build: This directory will contain the built version of your block. It’s generated when you run the build command.
  • block.json: This file defines the metadata and attributes of your block.

Building and Activating the Plugin

Before you can use the block in your WordPress site, you need to build it. In your terminal, run:

npm run build

This command will transpile your code and bundle it into a single JavaScript file in the build directory.

Now, go to your WordPress admin dashboard and navigate to “Plugins”. You should see your new plugin listed. Click “Activate” to activate it.

Testing the Block

Go to a post or a page, click the “+” button to add a new block, and look for your block. It will appear under the category “Widgets”. Add it to the post or page and see how it looks. The default block includes a simple editable text field.

This is just a basic block. In the next sections, we’ll learn how to customize it and add more functionalities.

Developing the Abel Display Block

Having created a basic block, we’re now going to customize it and add more advanced functionalities. Our goal is to create a block that displays a grid of animated shapes, each shape containing a post’s information.

Updating block.json

We start by defining our block attributes in the block.json file. These attributes will be used to save the state of our block. Update your block.json file to look something like this:

{
  "apiVersion": 2,
  "name": "create-block/abel-display",
  "title": "Abel Display",
  "category": "widgets",
  "parent": [],
  "icon": "smiley",
  "description": "Example block written with ESNext standard and JSX support – build step required.",
  "attributes": {
    "shapesData": {
      "type": "array",
      "default": []
    }
  },
  "supports": {
    "html": false,
    "align": true
  },
  "textdomain": "abel-display",
  "editorScript": "file:./build/index.js",
  "script": "file:./build/index.js",
  "editorStyle": "file:./build/index.css",
  "style": "file:./build/style-index.css"
}

Customizing the Edit Function

Next, let’s customize the edit.js file. This file controls how our block appears in the Gutenberg editor. We’ll add a useEffect hook to fetch posts from the WordPress REST API when the block first loads. We’ll also map over our shapesData attribute to display each shape. Add the following code to edit.js:

// Import dependencies
import { useEffect, useState } from '@wordpress/element';
import apiFetch from '@wordpress/api-fetch';
// Edit function
export default function Edit( { attributes, setAttributes } ) {
  const { shapesData } = attributes;
  const [posts, setPosts] = useState([]);
  // Fetch posts on mount
  useEffect(() => {
    apiFetch({ path: '/wp/v2/posts?_fields=id,title,link' })
      .then(setPosts)
      .catch(console.error);
  }, []);
  // Update shapesData when posts change
  useEffect(() => {
    if (posts.length > 0) {
      setAttributes({
        shapesData: posts.map(post => ({
          id: post.id,
          title: post.title.rendered,
          link: post.link
        }))
      });
    }
  }, [posts]);
  // Render shapes
  return (
    <div className="abel-display">
      {shapesData.map(shape => (
        <div className="shape" key={shape.id}>
          <h2>{shape.title}</h2>
          <a href={shape.link}>Read more</a>
        </div>
      ))}
    </div>
  );
}

Customizing the Save Function

Now, let’s customize the save.js file. This file controls how our block appears on the front end. We’ll simply return null from the save function, since we’ll be generating the shapes dynamically in the front end using JavaScript. Update your save.js file to look like this:

// Save function
export default function Save() {
  return null;
}

Adding Tingle.js to the Project

Tingle is a simple modal plugin written in pure JavaScript

Tingle.js is a minimalist and easy-to-use modal plugin written in pure JavaScript. We chose Tingle.js for this project because of its simplicity and flexibility, which make it a great fit for creating the modal functionality in our Gutenberg block.

To install Tingle.js via npm, navigate to your project directory in your terminal and run:

npm install tingle.js

This command will add Tingle.js to your node_modules folder and list it as a dependency in your package.json file.

To import Tingle.js into your JavaScript file, use an ES6 import statement at the top of your js file:

import tingle from 'tingle.js';

You also need to include the Tingle.js CSS file for the modal to display correctly. You can import it directly into your JavaScript file, but this requires a bundler like Webpack to handle CSS imports. If your project setup doesn’t support importing CSS in JavaScript, you will need to add the Tingle.js CSS file to your project separately.

First, find the tingle.css file in your node_modules folder. It should be located in node_modules/tingle.js/dist/tingle.css.

Then, you can enqueue the CSS in your plugin’s PHP file like this:

function abel_display_block_enqueue_assets() {
    wp_enqueue_style(
        'tingle-css',
        plugins_url( 'path/to/tingle.css', __FILE__ )
    );
}
add_action( 'enqueue_block_assets', 'abel_display_block_enqueue_assets' );

Replace 'path/to/tingle.css' with the actual path to the tingle.css file relative to your PHP file. The 'enqueue_block_assets' action is used to enqueue assets both on the frontend and in the editor.

This ensures that the Tingle.js CSS is loaded whenever your block is being used, which is necessary for the modal to display correctly. Now you’re ready to continue developing your block!

Developing the Front-End Script

The animate.js file will be responsible for the rendering and animation of the shapes in the front end. This script will read the post data embedded in the HTML by the save function, create the shapes, and animate them.

For simplicity, let’s say that we have a function startAnimation that kicks off the animation process. This function will find all the shape elements, and for each one, it will add an event listener that opens a modal with the post’s details when the shape is clicked.

Here is a simplified example of what animate.js might look like:

// Import the necessary dependencies
import tingle from 'tingle.js';
// startAnimation function
function startAnimation() {
  const shapes = document.querySelectorAll('.shape');
  // Add an event listener to each shape
  shapes.forEach((shape) => {
    shape.addEventListener('click', () => {
      // Parse the post data from the shape's data attribute
      const postData = JSON.parse(shape.dataset.post);
      // Create a new tingle modal
      const modal = new tingle.modal({
        footer: false,
        stickyFooter: false,
        closeMethods: ['overlay', 'button', 'escape'],
        closeLabel: "Close",
        cssClass: ['custom-class-1', 'custom-class-2'],
        onOpen: () => {
          console.log('modal open');
        },
        onClose: () => {
          console.log('modal closed');
        },
      });
      // Set the modal content using the post data
      modal.setContent(`
        <h2>${postData.title}</h2>
        <img src="${postData.img_src}" alt="${postData.title}">
        <a href="${postData.link}">Read more</a>
      `);
      // Open the modal
      modal.open();
    });
  });
}
// Call startAnimation when the DOM is fully loaded
document.addEventListener('DOMContentLoaded', startAnimation);

The animate.js script is attached to the front-end view of the block. It initializes the animation when the document content is fully loaded. Each shape is associated with an event listener, which triggers a modal with more information about the post when clicked.

Please note that this is a simplified example. The actual animate.js script would include more logic for creating the shapes, animating them, and changing their colors and shapes at random intervals. I’ll explain those parts in the next section.

Now, compile your changes with the npm run build command and test your block. Add it to a post, and you should see your animated shapes in the editor. Save the post and view it on the front end, and you should see the shapes there as well. When you click a shape, it should open a modal with the corresponding post’s details.

Using npm start for Development

When you’re actively developing your block, it’s beneficial to use npm start instead of npm run build. The npm start command initializes a development server that automatically recompiles your code whenever you save a file. This feature, known as hot reloading, can significantly speed up your development process.

To use this feature, open your terminal, navigate to your plugin’s directory, and run the command:

npm start

You should see output indicating that the development server has started and is watching your files for changes. Now, every time you save a file in your plugin, the code will automatically recompile, and you can see the changes in real time in the WordPress editor.

However, please note that the compiled code from npm start is not optimized for production. It includes extra code to aid in debugging and is generally larger and slower than the production code. When you’re done developing and ready to publish your plugin, you should stop the development server (Ctrl + C in the terminal) and run npm run build one final time to generate the optimized production code.

Now that we have a development server running and automatically recompiling our code, we can continue developing the Abel Display Block. In the next section, we will go over the PHP code that registers our block and enqueues the editor script.

Registering the Block and Enqueueing the Editor Script

The next step is to register our block with WordPress and tell it to load our editor script when the block editor is being used. This is done in PHP, typically in your plugin’s main PHP file. Here’s the code you need:

function abel_display_block_register_block() {
    wp_register_script(
        'abel-display-block-editor',
        plugins_url( 'build/index.js', __FILE__ ),
        array( 'wp-blocks', 'wp-element', 'wp-editor', 'wp-components' ),
        filemtime( plugin_dir_path( __FILE__ ) . 'build/index.js' )
    );
    register_block_type( 'abel/display-block', array(
        'editor_script' => 'abel-display-block-editor',
    ) );
}
add_action( 'init', 'abel_display_block_register_block' );

Let’s break down what this code does:

  • wp_register_script: This function registers a JavaScript file with WordPress. The first argument is a handle that is used to refer to the script. The second argument is the URL of the script file, which we generate using the plugins_url function. The third argument is an array of dependencies — other scripts that must be loaded before our script. The final argument is the file modification time of our script file, which is used as the version number. This ensures that if we modify our script, the new version will be used, even if the old version is cached.
  • register_block_type: This function registers a block type with WordPress. The first argument is the block name, which must be unique and consist of a namespace and a block name, separated by a slash. The second argument is an array of options for the block. In this case, we are specifying the handle of the editor script that should be enqueued when the block editor is loaded.
  • add_action: This function tells WordPress to call our block registration function during the init action, which happens after WordPress has finished loading but before any headers are sent. Most of WordPress’s functionality is available at this stage, and it is the recommended action to use when registering blocks.

That’s it! With this code in place, WordPress now knows about our block and will load our editor script when the block editor is loaded. Next, we’ll discuss creating the edit function for our block, which defines the editor interface and functionality.

Developing the Edit Function

The edit function is where we define how our block will behave in the WordPress editor. We want to be able to preview the animation of the shapes in the editor, just like on the front-end.

Our edit function in block.js starts by importing and using the abelDisplayAnimate function:

import abelDisplayAnimate from './animate';
export default function Edit( { attributes, setAttributes } ) {
  // Call the animation function when the block is being edited
  abelDisplayAnimate();
  ...
}

Here, abelDisplayAnimate is the function we exported from our animate.js file. We call this function inside Edit so that the animation starts whenever the block is being edited.

Next, we use React’s useEffect hook to call abelDisplayAnimate whenever the block updates:

import { useEffect } from '@wordpress/element';
...
export default function Edit( { attributes, setAttributes } ) {
  useEffect( () => {
    abelDisplayAnimate();
  }, [ attributes ] );
  ...
}

This ensures that the animation updates whenever the block’s attributes change. We pass [attributes] as the second argument to useEffect to specify that the effect should only rerun if attributes changes.

Finally, we return the JSX for our block’s edit view. For simplicity, we’ll make this the same as the save view:

return (
  <div id="abel-wrapper">
    { attributes.shapesData.map( ( shapeData ) => (
      <a
        href={ shapeData.link }
        className={ `shape ${ shapeData.color } ${ shapeData.shape }` }
        data-post={ JSON.stringify( shapeData ) }
      ></a>
    ) ) }
  </div>
);

This creates a div with the ID of abel-wrapper, and inside this div, we map over attributes.shapesData to create a shape for each object in the array.

Each shape is a link with the classes shape, the color of the shape, and the shape type. We also add a data-post attribute that contains the shape data as a JSON string. This will be used in our animation script to create the modal when a shape is clicked.

That’s it for the edit function! Next, we will move on to developing the save function for our block.

Developing the Save Function

The save function is responsible for rendering the block on the front end. It must return the final markup that should be saved to the post content.

Similar to the edit function, our save function will return the same JSX to keep the markup consistent between the editor and the front-end.

Here’s the save function in block.js:

export default function Save( { attributes } ) {
  return (
    <div id="abel-wrapper">
      { attributes.shapesData.map( ( shapeData ) => (
        <a
          href={ shapeData.link }
          className={ `shape ${ shapeData.color } ${ shapeData.shape }` }
          data-post={ JSON.stringify( shapeData ) }
        ></a>
      ) ) }
    </div>
  );
}

As you can see, the save function is very similar to the edit function. It maps over attributes.shapesData to create a shape for each object in the array, and each shape is a link with the classes shape, the color of the shape, and the shape type.

We also add a data-post attribute that contains the shape data as a JSON string. This attribute is necessary for our animation script to create the modal when a shape is clicked.

Registering the Block

With our edit and save functions complete, we can now register our block.

In block.js, we import the registerBlockType function from @wordpress/blocks and call it with our block name and configuration object:

import { registerBlockType } from '@wordpress/blocks';
import Edit from './edit';
import Save from './save';
registerBlockType( 'create-block/abel-display', {
  apiVersion: 2,
  title: 'Abel Display',
  description: 'Display a grid of animated shapes that represent posts.',
  category: 'widgets',
  icon: 'heart',
  supports: {
    html: false,
  },
  attributes: {
    shapesData: {
      type: 'array',
      default: [],
    },
  },
  edit: Edit,
  save: Save,
} );

The registerBlockType function takes two arguments: the block name and a configuration object. The block name must be a string that contains a namespace prefix, and the configuration object defines the block’s behavior and how it should be rendered.

The attributes property in the configuration object is where we define our block’s attributes. In this case, we have one attribute, shapesData, which is an array that will contain the data for each shape.

The edit and save properties are where we specify our edit and save functions, which we’ve imported at the top of our file.

With this, our block is ready to be built and tested in WordPress!

Building and Testing the Plugin

Before you can test your new Gutenberg block, you’ll need to build the plugin. If you recall from our setup, we have a build script configured in our package.json file. To run this script, open your terminal, navigate to your plugin directory, and run:

npm run build

This will compile your code and generate a build directory in your plugin’s folder.

Once the build process is complete, you can now activate the plugin in your WordPress dashboard and start using your new block!

Remember to always run the build command after making changes to your code to ensure the latest version of your block is used.

Conclusion

Creating custom Gutenberg blocks might seem daunting at first, but with the right approach and tools, it can be a smooth and rewarding process. In this tutorial, we’ve learned how to create a Gutenberg block plugin from scratch using the @wordpress/create-block package.

We have also delved into the block’s structure, developing both the editor and the front-end views, and adding interactive animations with an external library, tingle.js, to create a modal for each shape in our grid.

This process demonstrates how customizable Gutenberg blocks are and how they can enhance the user experience on your WordPress site. While our block is relatively simple, you can create much more complex blocks by adding more attributes and refining the edit and save functions.

Remember, Gutenberg is the future of WordPress, and learning how to create custom blocks is a valuable skill for any WordPress developer.

We hope you’ve found this tutorial helpful and are now more comfortable with Gutenberg block development. As always, happy coding!

References and Further Reading

Next steps

As we wrap up our journey of building a custom Gutenberg block, it’s essential to remember that learning and improvement are continuous processes. With this foundation, you can continue to enhance your WordPress projects and take them to the next level.

In our upcoming article, we will take a deeper dive into advanced Gutenberg block development. We’ll explore features like nesting blocks, applying block styles, and creating dynamic blocks that interact with your WordPress database. We’ll also look at more complex animations and interactions, and how you can use modern JavaScript tools and libraries to create truly engaging experiences for your users.

Stay tuned for more in-depth knowledge, hands-on examples, and actionable insights. Happy coding!

About The Author

Scroll to Top