# WordPress Gutenberg Block Editor
Gutenberg is WordPress's modern block-based editor that revolutionizes content creation by treating all content elements (paragraphs, images, headings, etc.) as individual blocks. Each block can be independently added, arranged, styled, and configured, enabling users to create rich media pages through an intuitive visual interface. The project eliminates the need for shortcodes and custom HTML, making content creation accessible while maintaining powerful customization capabilities for developers.
The architecture is organized as a monorepo with 100+ packages, each serving specific purposes: core packages handle block logic and data management, the block library provides 100+ pre-built blocks, editor packages deliver the full editing experience, and utility packages offer helper functions, hooks, and development tools. This modular design enables developers to extend WordPress with custom blocks, integrate REST API data, build admin interfaces with reusable components, and create full site editing experiences with template customization.
## Block Registration
Register a new custom block type with namespace, attributes, and edit/save functions.
```js
import { registerBlockType } from '@wordpress/blocks';
import { __ } from '@wordpress/i18n';
import { useBlockProps, RichText } from '@wordpress/block-editor';
registerBlockType( 'my-plugin/testimonial', {
title: __( 'Testimonial' ),
icon: 'format-quote',
category: 'widgets',
attributes: {
author: {
type: 'string',
source: 'html',
selector: '.author',
},
quote: {
type: 'string',
source: 'html',
selector: '.quote',
},
},
edit: ( { attributes, setAttributes } ) => {
const blockProps = useBlockProps();
return (
setAttributes( { quote } ) }
placeholder={ __( 'Enter testimonial...' ) }
/>
setAttributes( { author } ) }
placeholder={ __( 'Author name' ) }
/>
);
},
save: ( { attributes } ) => {
const blockProps = useBlockProps.save();
return (
{ attributes.quote }
{ attributes.author }
);
},
} );
```
## Block Editor Provider
Create a standalone block editor instance with state management.
```js
import { BlockEditorProvider, BlockList, WritingFlow } from '@wordpress/block-editor';
import { useState } from '@wordpress/element';
import { createBlock } from '@wordpress/blocks';
function CustomEditor() {
const [ blocks, updateBlocks ] = useState( [
createBlock( 'core/paragraph', { content: 'Hello World' } )
] );
return (
);
}
```
## Inspector Controls
Add custom controls to the block settings sidebar for configuration options.
```js
import { useBlockProps, InspectorControls, BlockControls, AlignmentToolbar } from '@wordpress/block-editor';
import { PanelBody, RangeControl, SelectControl, ToggleControl } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
export default function Edit( { attributes, setAttributes } ) {
const { columns, showImages, imageSize, alignment } = attributes;
const blockProps = useBlockProps();
return (
<>
setAttributes( { alignment } ) }
/>
setAttributes( { columns } ) }
min={ 1 }
max={ 4 }
/>
setAttributes( { showImages } ) }
/>
{ showImages && (
setAttributes( { imageSize } ) }
/>
) }
{/* Block content */}
>
);
}
```
## Inner Blocks
Create container blocks that accept nested child blocks with template configuration.
```js
import { useBlockProps, InnerBlocks } from '@wordpress/block-editor';
const ALLOWED_BLOCKS = [ 'core/heading', 'core/paragraph', 'core/image', 'core/list' ];
const TEMPLATE = [
[ 'core/heading', { level: 2, placeholder: 'Section title...' } ],
[ 'core/paragraph', { placeholder: 'Add description...' } ],
];
export default function Edit() {
const blockProps = useBlockProps( { className: 'section-container' } );
return (
);
}
export function Save() {
const blockProps = useBlockProps.save( { className: 'section-container' } );
return (
);
}
```
## Data Store Creation
Create a custom Redux store for application state management with actions, selectors, and resolvers.
```js
import { createReduxStore, register } from '@wordpress/data';
import apiFetch from '@wordpress/api-fetch';
const DEFAULT_STATE = {
products: {},
cart: [],
isLoading: false,
};
const store = createReduxStore( 'my-shop', {
reducer( state = DEFAULT_STATE, action ) {
switch ( action.type ) {
case 'SET_PRODUCT':
return {
...state,
products: {
...state.products,
[ action.id ]: action.product,
},
};
case 'ADD_TO_CART':
return {
...state,
cart: [ ...state.cart, action.productId ],
};
case 'SET_LOADING':
return {
...state,
isLoading: action.isLoading,
};
}
return state;
},
actions: {
setProduct( id, product ) {
return { type: 'SET_PRODUCT', id, product };
},
addToCart( productId ) {
return { type: 'ADD_TO_CART', productId };
},
setLoading( isLoading ) {
return { type: 'SET_LOADING', isLoading };
},
},
selectors: {
getProduct( state, id ) {
return state.products[ id ];
},
getCart( state ) {
return state.cart;
},
isLoading( state ) {
return state.isLoading;
},
},
resolvers: {
*getProduct( id ) {
yield { type: 'SET_LOADING', isLoading: true };
try {
const product = yield apiFetch( {
path: `/wp/v2/products/${ id }`
} );
return { type: 'SET_PRODUCT', id, product };
} finally {
yield { type: 'SET_LOADING', isLoading: false };
}
},
},
} );
register( store );
```
## useSelect Hook
Retrieve data from stores with automatic re-rendering on changes.
```js
import { useSelect } from '@wordpress/data';
import { store as blockEditorStore } from '@wordpress/block-editor';
import { store as coreStore } from '@wordpress/core-data';
function PostInfo() {
const { blockCount, selectedBlocks, postTitle, hasUnsavedChanges } = useSelect(
( select ) => {
const editor = select( blockEditorStore );
const editorStore = select( 'core/editor' );
return {
blockCount: editor.getBlockCount(),
selectedBlocks: editor.getSelectedBlockClientIds(),
postTitle: editorStore.getCurrentPost().title,
hasUnsavedChanges: editorStore.hasChangedContent(),
};
},
[]
);
return (
{ postTitle }
Total blocks: { blockCount }
Selected: { selectedBlocks.length }
{ hasUnsavedChanges &&
⚠️ Unsaved changes }
);
}
```
## useDispatch Hook
Access store actions for state mutations.
```js
import { useDispatch } from '@wordpress/data';
import { store as noticesStore } from '@wordpress/notices';
import { store as coreStore } from '@wordpress/core-data';
function SaveButton( { postId } ) {
const { createNotice } = useDispatch( noticesStore );
const { saveEntityRecord, editEntityRecord } = useDispatch( coreStore );
const handleSave = async () => {
try {
await saveEntityRecord( 'postType', 'post', postId );
createNotice( 'success', 'Post saved successfully!', {
type: 'snackbar',
isDismissible: true,
} );
} catch ( error ) {
createNotice( 'error', 'Failed to save post.', {
type: 'snackbar',
} );
}
};
const updateTitle = ( title ) => {
editEntityRecord( 'postType', 'post', postId, { title } );
};
return (
Save Post
);
}
```
## Entity Record Hooks
Fetch and manipulate WordPress entities (posts, pages, users) with built-in loading states and save functions.
```js
import { useEntityRecord, useEntityRecords } from '@wordpress/core-data';
import { Spinner, TextControl, Button } from '@wordpress/components';
function PageEditor( { pageId } ) {
const { record: page, isResolving, edit, save, hasEdits } = useEntityRecord(
'postType',
'page',
pageId
);
if ( isResolving ) return ;
return (
edit( { title } ) }
/>
edit( { slug } ) }
/>
Save Changes
);
}
function PagesList() {
const { records: pages, isResolving } = useEntityRecords(
'postType',
'page',
{
per_page: 20,
status: 'publish',
orderby: 'date',
order: 'desc',
}
);
if ( isResolving ) return ;
return (
{ pages.map( page => (
{ page.title.rendered }
{ page.slug }
) ) }
);
}
```
## API Fetch
Make REST API requests with automatic nonce handling and error management.
```js
import apiFetch from '@wordpress/api-fetch';
import { addQueryArgs } from '@wordpress/url';
// GET request
async function fetchPosts() {
try {
const posts = await apiFetch( {
path: '/wp/v2/posts'
} );
return posts;
} catch ( error ) {
console.error( 'Failed to fetch posts:', error );
}
}
// GET with query parameters
async function searchPosts( searchTerm ) {
const posts = await apiFetch( {
path: addQueryArgs( '/wp/v2/posts', {
search: searchTerm,
per_page: 10,
status: 'publish',
_fields: 'id,title,excerpt',
} )
} );
return posts;
}
// POST request
async function createPost( postData ) {
const newPost = await apiFetch( {
path: '/wp/v2/posts',
method: 'POST',
data: {
title: postData.title,
content: postData.content,
status: 'draft',
categories: [ 1, 5 ],
tags: [ 12, 34 ],
},
} );
return newPost;
}
// PUT/PATCH request
async function updatePost( postId, updates ) {
const updatedPost = await apiFetch( {
path: `/wp/v2/posts/${ postId }`,
method: 'POST',
data: updates,
} );
return updatedPost;
}
// DELETE request
async function deletePost( postId ) {
await apiFetch( {
path: `/wp/v2/posts/${ postId }`,
method: 'DELETE',
} );
}
// Custom middleware
apiFetch.use( ( options, next ) => {
console.log( 'API Request:', options.path );
const result = next( options );
result.then( ( data ) => {
console.log( 'API Response:', data );
} );
return result;
} );
```
## Block Parsing and Serialization
Parse HTML content into block objects and serialize blocks back to HTML.
```js
import { parse, serialize, createBlock } from '@wordpress/blocks';
// Parse HTML string to blocks
const htmlContent = `
First paragraph
Section Title
`;
const blocks = parse( htmlContent );
// Returns: Array of block objects with name, attributes, innerBlocks
// Create new blocks programmatically
const paragraphBlock = createBlock( 'core/paragraph', {
content: 'Hello World',
align: 'center',
} );
const headingBlock = createBlock( 'core/heading', {
content: 'My Title',
level: 2,
} );
const galleryBlock = createBlock( 'core/gallery', {
images: [
{ id: 1, url: 'https://example.com/image1.jpg' },
{ id: 2, url: 'https://example.com/image2.jpg' },
],
columns: 3,
} );
// Serialize blocks to HTML
const newBlocks = [ paragraphBlock, headingBlock, galleryBlock ];
const htmlOutput = serialize( newBlocks );
// Returns: "Hello World
..."
```
## Block Transforms
Enable conversion between different block types and from/to shortcodes.
```js
import { createBlock } from '@wordpress/blocks';
const transforms = {
from: [
{
type: 'block',
blocks: [ 'core/paragraph' ],
transform: ( { content } ) => {
return createBlock( 'my-plugin/custom-quote', {
text: content,
} );
},
},
{
type: 'block',
blocks: [ 'core/list' ],
transform: ( { values } ) => {
return values.split( '' ).map( ( item ) =>
createBlock( 'my-plugin/list-item', {
content: item.replace( /<\/?li>/g, '' ),
} )
);
},
},
{
type: 'shortcode',
tag: 'testimonial',
attributes: {
author: {
type: 'string',
shortcode: ( { named: { author } } ) => author,
},
quote: {
type: 'string',
shortcode: ( { named: { quote } } ) => quote,
},
},
},
{
type: 'raw',
selector: 'blockquote.testimonial',
schema: {
blockquote: {
children: {
p: { children: {} },
cite: { children: {} },
},
},
},
transform: ( node ) => {
return createBlock( 'my-plugin/testimonial', {
quote: node.querySelector( 'p' ).textContent,
author: node.querySelector( 'cite' ).textContent,
} );
},
},
],
to: [
{
type: 'block',
blocks: [ 'core/paragraph' ],
transform: ( { text } ) => {
return createBlock( 'core/paragraph', {
content: text,
} );
},
},
{
type: 'block',
blocks: [ 'core/quote' ],
transform: ( { text, author } ) => {
return createBlock( 'core/quote', {
value: `${ text }
`,
citation: author,
} );
},
},
],
};
export default transforms;
```
## Server-Side Rendered Blocks
Create dynamic blocks that render on the server with PHP while providing editor preview.
```php
'render_latest_posts_block',
) );
}
add_action( 'init', 'register_latest_posts_block' );
function render_latest_posts_block( $attributes, $content ) {
$posts_per_page = isset( $attributes['postsPerPage'] ) ? $attributes['postsPerPage'] : 5;
$category = isset( $attributes['category'] ) ? $attributes['category'] : '';
$args = array(
'posts_per_page' => $posts_per_page,
'post_status' => 'publish',
);
if ( ! empty( $category ) ) {
$args['category_name'] = $category;
}
$posts = get_posts( $args );
if ( empty( $posts ) ) {
return 'No posts found.
';
}
ob_start();
?>
setAttributes( { postsPerPage } ) }
min={ 1 }
max={ 20 }
/>
setAttributes( { category } ) }
/>
>
);
}
```
## Rich Text Formatting
Create custom inline formats for rich text content with toolbar controls.
```js
import { registerFormatType, toggleFormat } from '@wordpress/rich-text';
import { RichTextToolbarButton } from '@wordpress/block-editor';
import { Icon } from '@wordpress/components';
// Register custom format
registerFormatType( 'my-plugin/highlight', {
title: 'Highlight',
tagName: 'mark',
className: 'highlight',
edit: ( { isActive, value, onChange } ) => {
return (
}
title="Highlight"
onClick={ () => {
onChange( toggleFormat( value, {
type: 'my-plugin/highlight',
} ) );
} }
isActive={ isActive }
/>
);
},
} );
// Register format with attributes
registerFormatType( 'my-plugin/tooltip', {
title: 'Tooltip',
tagName: 'span',
className: 'has-tooltip',
attributes: {
'data-tooltip': 'data-tooltip',
},
edit: ( { isActive, value, onChange, contentRef } ) => {
const tooltipText = isActive ? value.activeFormats?.find(
format => format.type === 'my-plugin/tooltip'
)?.attributes?.[ 'data-tooltip' ] : '';
return (
{
const text = window.prompt( 'Enter tooltip text:', tooltipText );
if ( text !== null ) {
onChange( toggleFormat( value, {
type: 'my-plugin/tooltip',
attributes: { 'data-tooltip': text },
} ) );
}
} }
isActive={ isActive }
/>
);
},
} );
```
## Hooks and Filters
Extend blocks and editor behavior using WordPress hooks system.
```js
import { addFilter } from '@wordpress/hooks';
import { createHigherOrderComponent } from '@wordpress/compose';
import { InspectorControls } from '@wordpress/block-editor';
import { PanelBody, TextControl } from '@wordpress/components';
// Add custom attribute to all blocks
addFilter(
'blocks.registerBlockType',
'my-plugin/custom-attribute',
( settings, name ) => {
return {
...settings,
attributes: {
...settings.attributes,
customId: {
type: 'string',
default: '',
},
},
};
}
);
// Add custom control to block inspector
const withInspectorControl = createHigherOrderComponent( ( BlockEdit ) => {
return ( props ) => {
const { attributes, setAttributes } = props;
const { customId } = attributes;
return (
<>
setAttributes( { customId } ) }
/>
>
);
};
}, 'withInspectorControl' );
addFilter(
'editor.BlockEdit',
'my-plugin/with-inspector-control',
withInspectorControl
);
// Modify block save output
addFilter(
'blocks.getSaveElement',
'my-plugin/add-custom-id',
( element, blockType, attributes ) => {
if ( attributes.customId ) {
return {
...element,
props: {
...element.props,
id: attributes.customId,
},
};
}
return element;
}
);
// Restrict allowed blocks
addFilter(
'blocks.registerBlockType',
'my-plugin/restrict-blocks',
( settings, name ) => {
// Hide specific blocks from inserter
if ( [ 'core/verse', 'core/freeform' ].includes( name ) ) {
return {
...settings,
supports: {
...settings.supports,
inserter: false,
},
};
}
return settings;
}
);
```
## Compose Utilities
Use composition helpers for managing component lifecycle and behavior.
```js
import {
useInstanceId,
useDebounce,
usePrevious,
useRefEffect,
useCopyToClipboard,
useMediaQuery,
} from '@wordpress/compose';
import { useState, useEffect } from '@wordpress/element';
function AdvancedInput( { onChange } ) {
const [ value, setValue ] = useState( '' );
const [ showSuccess, setShowSuccess ] = useState( false );
// Generate unique ID for accessibility
const inputId = useInstanceId( AdvancedInput, 'input' );
// Debounce value changes
const debouncedValue = useDebounce( value, 500 );
// Track previous value
const previousValue = usePrevious( value );
// Responsive behavior
const isMobile = useMediaQuery( '(max-width: 768px)' );
// Copy to clipboard
const copyRef = useCopyToClipboard( value, () => {
setShowSuccess( true );
setTimeout( () => setShowSuccess( false ), 2000 );
} );
// Focus management with cleanup
const inputRef = useRefEffect( ( node ) => {
if ( ! isMobile ) {
node.focus();
}
return () => {
console.log( 'Input unmounted' );
};
}, [ isMobile ] );
// Call onChange with debounced value
useEffect( () => {
if ( debouncedValue !== previousValue ) {
onChange?.( debouncedValue );
}
}, [ debouncedValue, previousValue, onChange ] );
return (
{ isMobile ? 'Input (Mobile)' : 'Input' }
setValue( e.target.value ) }
/>
Copy
{ showSuccess && ✓ Copied! }
);
}
```
## Slot Fill System
Create extensible plugin areas using slots and fills for adding UI in specific locations.
```js
import { Fill, Slot, SlotFillProvider } from '@wordpress/components';
import { PluginDocumentSettingPanel } from '@wordpress/edit-post';
import { registerPlugin } from '@wordpress/plugins';
// Create custom slot in your component
function EditorSidebar() {
return (
Settings
);
}
// Fill the slot from another component/plugin
function CustomPanel() {
return (
This content is injected via Fill
);
}
// Wrap in SlotFillProvider
function App() {
return (
);
}
// Use WordPress built-in slots via plugins
registerPlugin( 'my-plugin-sidebar', {
render: () => {
return (
Custom settings content
);
},
} );
```
WordPress Gutenberg provides a comprehensive platform for building modern content editors and custom WordPress experiences. The primary use cases include developing custom blocks for unique content types, extending the editor with plugins that add panels and controls, building block-based themes with template customization, programmatically managing post content through data stores, creating admin interfaces using the component library, and implementing full site editing capabilities. The modular architecture enables developers to use individual packages independently or combine them for complete solutions.
The integration patterns center around block registration with `registerBlockType`, state management through `@wordpress/data` stores with `useSelect` and `useDispatch` hooks, entity manipulation via `@wordpress/core-data` for WordPress REST API integration, and component composition using `@wordpress/components` UI library. The hooks system (`@wordpress/hooks`) enables extending existing blocks and editor behavior, while the SlotFill pattern provides extensible UI areas. Server-side rendering supports dynamic content generation, and the transform API enables seamless conversion between different block types and content formats. This ecosystem empowers developers to create sophisticated content editing experiences while maintaining consistency with WordPress design patterns.