Create Your First Gutenberg Block

In this tutorial we are going to create a super-simple Gutenberg block. It is going to be a subscription form that is not even editable for now, but we are going to make it so in our next tutorials in this series.

Create a Gutenberg block WordPress
Once created, the block can be used as many times in post content as you need.

Setting Up Developer Environment

In this tutorial it is going to be a lot of JSX because, of course, I don’t recommend to create Gutenberg blocks with PHP with the help of any plugins. It could be OK for Plugin Sidebar fields, but for blocks – never.

So, our first goal is to set up an environment that allows us to work with JSX.

Install npm and create a project

First things first you have to install Node.js and npm, you can check it in terminal with npm -v and node -v, and, if you’ve not installed it yet, please go to their website and do it.

Then create a folder in your wp-content/plugins (our Gutenberg block will be a plugin, though it could be a part of a theme of course), open it as a project in your favourite code editor (I use Atom for now), open terminal for this specific project and run npm init.

Oh and yes, I assume that you already have a local server installed, I am using MAMP.

File structure

Now it is enough to create 6 files:

  • misha-block.php – it is the main plugin file.
  • block.json – we will talk about it just a little bit.
  • src/index.js – it is where we are going to write the code of our Gutenberg block
  • build/index.js – this file is for browsers, minified code of our Gutenberg block, we are going to include it later.

I think we will also need to add some CSS styles for our block, it can be any .css file in any folder of the plugin, it could be assets/style.css and assets/front.css for example.

Using @wordpress/scripts

@wordpress/scripts is a super-useful tool that allows to bypass Webpack and Babel installation and configuration. It can be installed with just two simple steps:

  1. Inside your project run npm install --save-dev @wordpress/scripts.
  2. Then just go to package.json, and add the following lines there:
"scripts": {
	"build": "wp-scripts build",
	"start": "wp-scripts start"

Now you just have two npm commands to make the work done:

  • npm run start – it watches for the changes in src/index.js file and every time you smash “Save” or Cmd + S it compiles the browser-ready code of the block in development mode. In order to exit the watching mode, press Ctrl + C (on Mac OS).
  • npm run build – it compiles the block code for production mode and minifies the result JavaScript. Run it when you finish the development work.


Since WordPress 5.8 we have a nice way of setting up the block metadata.

Before that it was kind of split between register_block_type() PHP function and registerBlockType() JavaScript function. I personally did all the stuff on the client side. Some were doing most of it it server-side.

Below is a simple example of block configuration file, I tried to use the bare minimum of what we will need for our block in this tutorial. The complete list of parameters you can find in the official documentation.

	"$schema": "",
	"apiVersion": 2,
	"name": "rudr/form",
	"title": "Subscription form",
	"description": "This block displays a subscription form",
	"category": "text",
	"editorScript": "file:./build/index.js",
	"editorStyle": "file:./assets/style.css",
	"style": "file:./assets/front.css"

So here we have:

  • name, title and description should’t make the questions appear except that the name is prefixed with “rudr/” – just namespacing the block name, that’s all.
  • category – in what group of blocks in Inserter the block will appear. The core ones are: media, design, widgets, theme, embed and text.
  • editorScript – the JavaScript file with the code of the block.
  • editorStyle – the CSS file with the styles of the block for the Editor.
  • style – the CSS file with the styles of the block for the frontend.

register_block_type() – Registering the Block in PHP

This function was used widely for block configuration and it has a lot of arguments by the way. But I prefer to use it this simple way and to provide all the data in block.json.

The code below is for our main file which is misha-block.php.

 * Plugin name: Block by Misha
 * Author: Misha Rudrastyh
 * Author URI:
 * Plugin URL:
add_action( 'init', 'rudr_register_in_php' );

function rudr_register_in_php() {

	register_block_type( __DIR__ );


Inside the function we just have to provide the directory path with block.json file. For example, in case we placed it into the /build directory, the argument of the function will look like this:

register_block_type( __DIR__ . '/build' );

registerBlockType() – Registering the Block in JavaScript

Below is the code for src/index.js file. As well as with register_block_type() function we don’t have many arguments here since everything is provided in block.json.

import { registerBlockType } from '@wordpress/blocks';

import Edit from './edit';
import Save from './save';
import metadata from './../block.json';

		edit: Edit,
		save: Save

I’ve seen a lot of guides where edit() and save() methods of registerBlockType() function are placed in separate files, and I find it very convenient. So we do it the same way here.


This function returns HTML code which is used to display a block in Gutenberg editor. And it is critical to understand.

It means that for our block we do need <form> or <button> tags, they will be in save() function below. But here we just have to use static HTML elements like <div> or <span> or <p>. That’s all!

In the next tutorial we are going to replace some of them with Editable and RichText components, but at this moment our block is not editable, so we will leave it for now.

In order to understand the below code you have to learn some basics about creating elements in React.

import { useBlockProps } from '@wordpress/block-editor';

export default function Edit() {
		<div {...useBlockProps()}>
			<h3>Like this post? Join my mailing list!</h3>
				<span>Email address</span>


This function should return HTML how the block is going to be displayed on the website pages and saved to database. That’s why here we’re using <form>, <input> and <button> tags.

import { useBlockProps } from '@wordpress/block-editor';

export default function Save() {
	return (
		<div {}>
			<h3>Like this post? Join my mailing list!</h3>
				<input type="email" placeholder="Enter your email address" />

You can the block files in my GitHub repository.

Misha Rudrastyh

Misha Rudrastyh

Hey guys and welcome to my website. For more than 10 years I've been doing my best to share with you some superb WordPress guides and tips for free.

Need some developer help? Contact me

Follow me on X