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.

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 nodejs.org 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 blockbuild/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:
- Inside your project run
npm install --save-dev @wordpress/scripts
. - 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 insrc/index.js
file and every time you smash “Save” orCmd + S
it compiles the browser-ready code of the block in development mode. In order to exit the watching mode, pressCtrl + 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.
block.json
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": "https://schemas.wp.org/trunk/block.json",
"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
anddescription
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
andtext
.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: https://rudrastyh.com
* Plugin URL: https://rudrastyh.com/gutenberg/create-a-block.html
*/
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';
registerBlockType(
metadata,
{
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.
edit()
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() {
return(
<div {...useBlockProps()}>
<h3>Like this post? Join my mailing list!</h3>
<p>
<span>Email address</span>
<span>Subscribe</span>
</p>
</div>
)
}
save()
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 {...useBlockProps.save()}>
<h3>Like this post? Join my mailing list!</h3>
<form>
<input type="email" placeholder="Enter your email address" />
<button>Subscribe</button>
</form>
</div>
);
}
You can the block files in my GitHub repository.

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
Thanks
Great
Great post, thanks!
But why I can’t do it when I create my custom theme? Why I must cheate some plugin for this? How I can create my custom block in my theme wordpress?
Thank you!
You can easily do it in a custom theme, why not
What’s your opinion about putting custom gutenberg block into the theme or as a plugin?
In this blog it’s said that having a plugin is better. https://jasonyingling.me/gutenberg-best-practices-for-blocks-and-themes/
But when developing, I prefer to have everything in one place instead of two.
And how would you add the custom gutenberg block into the theme instead as a plugin?
My opinion is that putting it in themes is ok 🙃
Hi in which file am I supposed to put the registerBlockType function?
Hi Kate,
In
src/index.js
.