How to Create Your First Gutenberg Custom Block

Custom blocks are the new way to extend WordPress content since the Block Editor shipped with WordPress 5.0. A block is a JavaScript + PHP component registered with registerBlockType() on the front end and register_block_type() on the PHP side. Here’s a minimal working example.

Problem: How do you create a custom Gutenberg block that renders dynamic PHP content on the front end and uses a React-based edit interface in the editor?

Solution: Register the block with register_block_type() in PHP and enqueue a JavaScript file built with @wordpress/scripts that exports registerBlockType() with edit and save functions — or use a render_callback in PHP for server-side rendering.

Register the block in PHP and enqueue the editor script:

add_action( 'enqueue_block_editor_assets', 'register_callout_block' );

function register_callout_block() {
    wp_enqueue_script(
        'my-callout-block',
        plugins_url( 'block.js', __FILE__ ),
        [ 'wp-blocks', 'wp-element', 'wp-editor', 'wp-components' ],
        filemtime( plugin_dir_path( __FILE__ ) . 'block.js' )
    );
}

The block JavaScript (block.js):

const { registerBlockType } = wp.blocks;
const { RichText }          = wp.editor;
const { __ }               = wp.i18n;

registerBlockType( 'my-plugin/callout', {
    title:    __( 'Callout Box', 'my-plugin' ),
    icon:     'megaphone',
    category: 'common',

    attributes: {
        content: {
            type:     'string',
            source:   'html',
            selector: 'p',
        },
    },

    edit( { attributes, setAttributes } ) {
        return (
            wp.element.createElement(
                'div',
                { className: 'callout-box' },
                wp.element.createElement( RichText, {
                    tagName:    'p',
                    value:      attributes.content,
                    onChange:   ( value ) => setAttributes( { content: value } ),
                    placeholder: __( 'Write your callout text…', 'my-plugin' ),
                } )
            )
        );
    },

    save( { attributes } ) {
        return wp.element.createElement(
            'div',
            { className: 'callout-box' },
            wp.element.createElement( RichText.Content, {
                tagName: 'p',
                value:   attributes.content,
            } )
        );
    },
} );

Add a front-end stylesheet for the block:

.callout-box {
    background: #fff3cd;
    border-left: 4px solid #ffc107;
    padding: 1rem 1.25rem;
    border-radius: 0 4px 4px 0;
}

NOTE: In WordPress 5.0, the wp.editor package contains RichText. This was later moved to wp.blockEditor in WordPress 5.2. If you're targeting 5.0 specifically, use wp.editor; for cross-version compatibility, check both packages.