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.