This article has been updated to reflect the standard way in latest WordPress version.
Creating a custom block isn’t an easy feat especially if you are not comfortable with pure JavaScript. Hopefully this guide can help you with that.
Before Making Custom Block
Is there an existing block that fits your need?
First, ask that question to yourself before wasting time to create redundant block.
For example I once need to create this design in Gutenberg:

Native block that fits the above is Media-Text. The part that we can’t create natively is the Colored box behind the image and the stars below the Heading.
So we simply create custom styles:
register_block_style( 'core/media-text', [ 'name' => 'half-bg', 'label' => 'Half Background' ] );
register_block_style( 'core/heading', [ 'name' => 'has-stars', 'label' => 'Has Stars' ] );
Now we can use pseudoselector ::before
and ::after
to create that design.
If none of the existing blocks fits your need, continue the tutorial below.
1. Register Assets & Block Type
Create asset files (CSS & JS), register them, then enqueue them using register_block_type()
function.
Block name’s format must be namespace/block-id
.
add_action( 'init', 'my_register_block_type' );
function my_register_block_type() {
// if Gutenberg is not active
if ( !function_exists( 'register_block_type' ) ) {
return;
}
// Register assets
$css_dir = get_stylesheet_directory_uri() . '/assets/css';
$js_dir = get_stylesheet_directory_uri() . '/assets/js';
wp_register_script( 'my-block-admin', $js_dir . '/my-block-admin.js', [ 'wp-blocks', 'wp-dom' ] , null, true );
wp_register_style( 'my-block-admin', $css_dir . '/my-block-admin.css', [ 'wp-edit-blocks' ] );
wp_register_style( 'my-block', $css_dir . '/my-block.css' );
// Register block type and enqueue the assets.
register_block_type( 'my/simple-text', [
'editor_style' => 'my-block-admin', // admin only
'editor_script' => 'my-block-admin', // admin only
'style' => 'my-block', // admin & public
] );
}
2. The Basic Script
The snippet below is a starting point for creating a custom block:
( function( blocks, element, blockEditor ) {
var el = element.createElement;
var RichText = blockEditor.RichText;
blocks.registerBlockType( 'my/simple-text', {
title: 'Simple Text (Custom)',
icon: 'universal-access-alt',
category: 'layout',
attributes: {},
edit: myEdit,
save: mySave
} );
// what's rendered in admin
function myEdit( _props ) {
}
// what's saved and rendered in frontend
function mySave() {
}
}( window.wp.blocks, window.wp.element, window.wp.blockEditor ) );
Let’s take a deeper look at some of registerBlockType()
parameters:
icon
– Click here for a list of available icons.attributes
– Defines how to extract the value from saved content. More info»edit
– Callback on how to render the content in the editor.save
– Callback on how to render the content before saved to the database.
3. Implementing Attributes, Edit, & Save
( function( blocks, element, blockEditor ) {
var el = element.createElement;
var RichText = blockEditor.RichText;
blocks.registerBlockType( 'my/simple-text', {
title: 'Simple Text (Custom)',
icon: 'universal-access-alt',
category: 'layout',
example: {},
// All the children inside 'p' will be extracted as 'text' variable.
attributes: {
text: { type: 'array', source: 'children', selector: 'p' }
},
edit: myEdit,
save: mySave,
} );
// Render RichText (field with basic toolbar) wrapped with 'p'
function myEdit( props ) {
return el( RichText, {
tagName: 'p',
onChange: _onChange,
value: props.attributes.text,
className: props.className,
} );
// when changed, update the attribute
function _onChange( newContent ) {
props.setAttributes( { text: newContent } );
}
}
// Save the content of RichText wrapped with 'p'
function mySave( props ) {
return el( RichText.Content, {
tagName: 'p',
className: props.className,
value: props.attributes.text,
} );
}
}( window.wp.blocks, window.wp.element, window.wp.blockEditor ) );
4. Check the Result


Conclusion
Creating a custom block is difficult and can get ugly. So always try to just use custom style if possible.
If you are interested to learn more, we made an extensive tutorial over on our Github:
Feel free to leave a question below regarding custom block 🙂
Shouldn't the path to the file in point 2 and 3 be '/assets/js/my-block-admin.js' - same as enqued scripts file?
Hi Bart, thanks for noticing the mistake. I have fixed it
[…] The JS is long and complex, so copy it from our gist. If you want to learn the syntax, check out our other tutorial. […]
I literally copied and pasted and it's not working on my website.
Is there something else I need to do?
Hi, your theme folder structure probably different from mine