Try Live
Add Docs
Rankings
Pricing
Enterprise
Docs
Install
Install
Docs
Pricing
Enterprise
More...
More...
Try Live
Rankings
Add Docs
WordPress Gutenberg
https://github.com/wordpress/gutenberg
Admin
Gutenberg is the codename for the new WordPress block editor, revolutionizing site building and
...
Tokens:
1,145,792
Snippets:
12,462
Trust Score:
9
Update:
3 days ago
Context
Skills
Chat
Benchmark
73
Suggestions
Latest
Show doc for...
Code
Info
Show Results
Context Summary (auto-generated)
Raw
Copy
Link
# Gutenberg - WordPress Block Editor Gutenberg is WordPress's modular block-based editor that revolutionizes content creation by treating each piece of content—paragraphs, images, galleries, and more—as independent "blocks" that can be added, arranged, and customized. The project serves as both a WordPress plugin for testing cutting-edge features and as the foundation for WordPress Core's editing experience, powering the post editor, site editor, and full-site editing capabilities. The Gutenberg monorepo contains over 100 npm packages that provide the building blocks for creating custom blocks, extending the editor, managing application state, and interacting with WordPress REST APIs. Key packages include `@wordpress/blocks` for block registration, `@wordpress/data` for state management, `@wordpress/components` for UI elements, `@wordpress/block-editor` for editor functionality, and `@wordpress/core-data` for WordPress entity management. --- ## registerBlockType - Register Custom Blocks The `registerBlockType` function is the primary method for creating new block types in WordPress. It accepts a unique block name (namespace/block-name format) and a configuration object defining the block's behavior, attributes, and rendering logic. Blocks can be registered via JavaScript or using a `block.json` metadata file for improved performance and automatic asset loading. ```jsx import { registerBlockType } from '@wordpress/blocks'; import { useBlockProps, RichText } from '@wordpress/block-editor'; import { __ } from '@wordpress/i18n'; registerBlockType( 'my-plugin/custom-card', { apiVersion: 3, title: __( 'Custom Card' ), description: __( 'A card block with title and content.' ), category: 'design', icon: 'index-card', keywords: [ 'card', 'box', 'container' ], attributes: { title: { type: 'string', source: 'html', selector: 'h3', }, content: { type: 'string', source: 'html', selector: 'p', }, }, supports: { color: { background: true, text: true, }, spacing: { padding: true, margin: true, }, }, edit: ( { attributes, setAttributes } ) => { const blockProps = useBlockProps( { className: 'my-custom-card', } ); return ( <div { ...blockProps }> <RichText tagName="h3" value={ attributes.title } onChange={ ( title ) => setAttributes( { title } ) } placeholder={ __( 'Card Title...' ) } /> <RichText tagName="p" value={ attributes.content } onChange={ ( content ) => setAttributes( { content } ) } placeholder={ __( 'Card content...' ) } /> </div> ); }, save: ( { attributes } ) => { const blockProps = useBlockProps.save( { className: 'my-custom-card', } ); return ( <div { ...blockProps }> <RichText.Content tagName="h3" value={ attributes.title } /> <RichText.Content tagName="p" value={ attributes.content } /> </div> ); }, } ); ``` --- ## block.json - Block Metadata File The `block.json` file is the recommended way to define block metadata, enabling server-side registration, automatic asset optimization, and Block Directory compatibility. WordPress automatically registers scripts and styles defined in the file and only loads them when the block is present on the page. ```json { "$schema": "https://schemas.wp.org/trunk/block.json", "apiVersion": 3, "name": "my-plugin/testimonial", "title": "Testimonial", "category": "widgets", "icon": "format-quote", "description": "Display a customer testimonial with quote and author.", "keywords": [ "quote", "review", "feedback" ], "version": "1.0.0", "textdomain": "my-plugin", "attributes": { "quote": { "type": "string", "source": "html", "selector": ".testimonial-quote" }, "author": { "type": "string", "source": "html", "selector": ".testimonial-author" }, "imageUrl": { "type": "string", "source": "attribute", "selector": "img", "attribute": "src" } }, "supports": { "align": [ "wide", "full" ], "color": { "background": true, "text": true, "gradients": true }, "typography": { "fontSize": true, "lineHeight": true }, "spacing": { "margin": true, "padding": true } }, "editorScript": "file:./index.js", "editorStyle": "file:./index.css", "style": "file:./style.css", "viewScript": "file:./view.js", "render": "file:./render.php" } ``` --- ## useSelect - Read Data from Stores The `useSelect` hook retrieves data from WordPress data stores with automatic re-rendering when the data changes. It accepts a mapping function that receives the `select` function and returns the desired data, plus an optional dependency array for memoization. ```jsx import { useSelect } from '@wordpress/data'; import { store as coreDataStore } from '@wordpress/core-data'; import { store as blockEditorStore } from '@wordpress/block-editor'; function PostMetaDisplay() { // Fetch multiple pieces of data in a single useSelect call const { posts, isLoading, currentPostType, selectedBlockClientId, blockCount } = useSelect( ( select ) => { const { getEntityRecords, hasFinishedResolution } = select( coreDataStore ); const { getSelectedBlockClientId, getBlockCount } = select( blockEditorStore ); const { getCurrentPostType } = select( 'core/editor' ); const query = { per_page: 5, status: 'publish' }; return { posts: getEntityRecords( 'postType', 'post', query ), isLoading: ! hasFinishedResolution( 'getEntityRecords', [ 'postType', 'post', query ] ), currentPostType: getCurrentPostType(), selectedBlockClientId: getSelectedBlockClientId(), blockCount: getBlockCount(), }; }, [] ); if ( isLoading ) { return <p>Loading posts...</p>; } return ( <div> <p>Current post type: { currentPostType }</p> <p>Selected block: { selectedBlockClientId || 'None' }</p> <p>Total blocks: { blockCount }</p> <ul> { posts?.map( ( post ) => ( <li key={ post.id }>{ post.title.rendered }</li> ) ) } </ul> </div> ); } ``` --- ## useDispatch - Dispatch Actions to Stores The `useDispatch` hook provides access to action creators from WordPress data stores, allowing you to modify state, save data, and trigger side effects. Actions dispatched through this hook automatically update the store and trigger re-renders in subscribed components. ```jsx import { useDispatch, useSelect } from '@wordpress/data'; import { store as coreDataStore } from '@wordpress/core-data'; import { store as noticesStore } from '@wordpress/notices'; import { Button, TextControl } from '@wordpress/components'; import { useState } from '@wordpress/element'; function PostTitleEditor( { postId } ) { const [ newTitle, setNewTitle ] = useState( '' ); // Get dispatch functions from multiple stores const { editEntityRecord, saveEditedEntityRecord } = useDispatch( coreDataStore ); const { createSuccessNotice, createErrorNotice } = useDispatch( noticesStore ); // Get current post data const { post, hasEdits, isSaving } = useSelect( ( select ) => { const { getEditedEntityRecord, hasEditsForEntityRecord, isSavingEntityRecord } = select( coreDataStore ); return { post: getEditedEntityRecord( 'postType', 'post', postId ), hasEdits: hasEditsForEntityRecord( 'postType', 'post', postId ), isSaving: isSavingEntityRecord( 'postType', 'post', postId ), }; }, [ postId ] ); const handleTitleChange = ( title ) => { setNewTitle( title ); // Edit the entity record (creates pending changes) editEntityRecord( 'postType', 'post', postId, { title } ); }; const handleSave = async () => { try { await saveEditedEntityRecord( 'postType', 'post', postId ); createSuccessNotice( 'Post title updated successfully!', { type: 'snackbar', isDismissible: true, } ); } catch ( error ) { createErrorNotice( `Failed to save: ${ error.message }`, { type: 'snackbar', } ); } }; return ( <div> <TextControl label="Post Title" value={ newTitle || post?.title || '' } onChange={ handleTitleChange } /> <Button variant="primary" onClick={ handleSave } disabled={ ! hasEdits || isSaving } isBusy={ isSaving } > { isSaving ? 'Saving...' : 'Save Title' } </Button> </div> ); } ``` --- ## createReduxStore - Create Custom Data Stores The `createReduxStore` function creates a custom data store with reducers, actions, selectors, and resolvers for asynchronous data fetching. Stores provide centralized state management and can be used across the entire application. ```js import apiFetch from '@wordpress/api-fetch'; import { createReduxStore, register } from '@wordpress/data'; const DEFAULT_STATE = { items: [], isLoading: false, error: null, selectedId: null, }; const actions = { setItems( items ) { return { type: 'SET_ITEMS', items }; }, setLoading( isLoading ) { return { type: 'SET_LOADING', isLoading }; }, setError( error ) { return { type: 'SET_ERROR', error }; }, selectItem( id ) { return { type: 'SELECT_ITEM', id }; }, // Thunk action for async operations fetchItems: () => async ( { dispatch } ) => { dispatch.setLoading( true ); dispatch.setError( null ); try { const items = await apiFetch( { path: '/my-plugin/v1/items' } ); dispatch.setItems( items ); } catch ( error ) { dispatch.setError( error.message ); } finally { dispatch.setLoading( false ); } }, deleteItem: ( id ) => async ( { dispatch, select } ) => { await apiFetch( { path: `/my-plugin/v1/items/${ id }`, method: 'DELETE' } ); const currentItems = select.getItems(); dispatch.setItems( currentItems.filter( item => item.id !== id ) ); }, }; const reducer = ( state = DEFAULT_STATE, action ) => { switch ( action.type ) { case 'SET_ITEMS': return { ...state, items: action.items }; case 'SET_LOADING': return { ...state, isLoading: action.isLoading }; case 'SET_ERROR': return { ...state, error: action.error }; case 'SELECT_ITEM': return { ...state, selectedId: action.id }; default: return state; } }; const selectors = { getItems( state ) { return state.items; }, getItemById( state, id ) { return state.items.find( item => item.id === id ); }, getSelectedItem( state ) { return state.items.find( item => item.id === state.selectedId ); }, isLoading( state ) { return state.isLoading; }, getError( state ) { return state.error; }, }; // Resolver for automatic data fetching const resolvers = { getItems: () => async ( { dispatch } ) => { dispatch.fetchItems(); }, }; const store = createReduxStore( 'my-plugin/items', { reducer, actions, selectors, resolvers, } ); register( store ); export default store; ``` --- ## useEntityRecord - Fetch and Edit Entity Records The `useEntityRecord` hook provides a convenient way to fetch, edit, and save WordPress entity records (posts, pages, users, etc.) with built-in loading states and mutation helpers. ```jsx import { useEntityRecord } from '@wordpress/core-data'; import { TextControl, Button, Spinner, Notice } from '@wordpress/components'; import { useState, useCallback } from '@wordpress/element'; function PageEditor( { pageId } ) { const { record: page, editedRecord, hasEdits, isResolving, isSaving, edit, save, } = useEntityRecord( 'postType', 'page', pageId ); const [ saveError, setSaveError ] = useState( null ); const handleTitleChange = useCallback( ( title ) => { edit( { title } ); }, [ edit ] ); const handleContentChange = useCallback( ( content ) => { edit( { content } ); }, [ edit ] ); const handleSave = async () => { setSaveError( null ); try { await save(); } catch ( error ) { setSaveError( error.message ); } }; if ( isResolving ) { return <Spinner />; } if ( ! page ) { return <Notice status="error">Page not found.</Notice>; } return ( <div className="page-editor"> { saveError && ( <Notice status="error" isDismissible onRemove={ () => setSaveError( null ) }> { saveError } </Notice> ) } <TextControl label="Page Title" value={ editedRecord.title || '' } onChange={ handleTitleChange } /> <TextControl label="Page Content" value={ editedRecord.content || '' } onChange={ handleContentChange } /> <p>Status: { page.status }</p> <p>Last modified: { new Date( page.modified ).toLocaleString() }</p> <Button variant="primary" onClick={ handleSave } disabled={ ! hasEdits || isSaving } isBusy={ isSaving } > { isSaving ? 'Saving...' : 'Save Changes' } </Button> </div> ); } ``` --- ## useEntityRecords - Fetch Multiple Records The `useEntityRecords` hook fetches collections of WordPress entity records with support for query parameters, pagination, and automatic resolution tracking. ```jsx import { useEntityRecords } from '@wordpress/core-data'; import { SearchControl, Spinner, SelectControl } from '@wordpress/components'; import { useState, useMemo } from '@wordpress/element'; function PostsListing() { const [ searchTerm, setSearchTerm ] = useState( '' ); const [ postStatus, setPostStatus ] = useState( 'publish' ); const [ perPage, setPerPage ] = useState( 10 ); // Build query parameters const queryArgs = useMemo( () => ( { per_page: perPage, status: postStatus, search: searchTerm || undefined, orderby: 'date', order: 'desc', } ), [ perPage, postStatus, searchTerm ] ); const { records: posts, isResolving, hasResolved, totalItems, totalPages, } = useEntityRecords( 'postType', 'post', queryArgs ); return ( <div className="posts-listing"> <div className="posts-filters"> <SearchControl value={ searchTerm } onChange={ setSearchTerm } placeholder="Search posts..." /> <SelectControl label="Status" value={ postStatus } options={ [ { label: 'Published', value: 'publish' }, { label: 'Draft', value: 'draft' }, { label: 'Pending', value: 'pending' }, ] } onChange={ setPostStatus } /> <SelectControl label="Per Page" value={ perPage } options={ [ { label: '5', value: 5 }, { label: '10', value: 10 }, { label: '25', value: 25 }, ] } onChange={ ( value ) => setPerPage( Number( value ) ) } /> </div> { isResolving && <Spinner /> } { hasResolved && posts?.length === 0 && ( <p>No posts found matching your criteria.</p> ) } { hasResolved && posts?.length > 0 && ( <> <p>Showing { posts.length } of { totalItems } posts ({ totalPages } pages)</p> <ul className="posts-list"> { posts.map( ( post ) => ( <li key={ post.id } className="post-item"> <h3>{ post.title.rendered }</h3> <span className="post-date"> { new Date( post.date ).toLocaleDateString() } </span> <span className="post-status">{ post.status }</span> </li> ) ) } </ul> </> ) } </div> ); } ``` --- ## apiFetch - WordPress REST API Requests The `apiFetch` utility simplifies making requests to the WordPress REST API with automatic nonce handling, JSON parsing, and middleware support for request/response transformation. ```js import apiFetch from '@wordpress/api-fetch'; import { addQueryArgs } from '@wordpress/url'; // GET request - Fetch posts async function fetchPosts() { try { const posts = await apiFetch( { path: '/wp/v2/posts', } ); console.log( 'Posts:', posts ); return posts; } catch ( error ) { console.error( 'Failed to fetch posts:', error ); throw error; } } // GET with query parameters async function searchPosts( searchTerm, page = 1 ) { const queryParams = { search: searchTerm, page, per_page: 10, _embed: true, // Include embedded data (author, featured media) }; const posts = await apiFetch( { path: addQueryArgs( '/wp/v2/posts', queryParams ), } ); return posts; } // POST request - Create a new post async function createPost( title, content, status = 'draft' ) { const newPost = await apiFetch( { path: '/wp/v2/posts', method: 'POST', data: { title, content, status, }, } ); return newPost; } // PUT request - Update a post async function updatePost( postId, updates ) { const updatedPost = await apiFetch( { path: `/wp/v2/posts/${ postId }`, method: 'PUT', data: updates, } ); return updatedPost; } // DELETE request - Trash a post async function trashPost( postId, force = false ) { const result = await apiFetch( { path: addQueryArgs( `/wp/v2/posts/${ postId }`, { force } ), method: 'DELETE', } ); return result; } // Custom endpoint with AbortController for cancellation async function fetchWithTimeout( path, timeoutMs = 5000 ) { const controller = new AbortController(); const timeoutId = setTimeout( () => controller.abort(), timeoutMs ); try { const result = await apiFetch( { path, signal: controller.signal, } ); clearTimeout( timeoutId ); return result; } catch ( error ) { clearTimeout( timeoutId ); if ( error.name === 'AbortError' ) { throw new Error( 'Request timed out' ); } throw error; } } // Upload media file async function uploadMedia( file ) { const formData = new FormData(); formData.append( 'file', file ); formData.append( 'title', file.name ); const media = await apiFetch( { path: '/wp/v2/media', method: 'POST', body: formData, } ); return media; } ``` --- ## InnerBlocks - Nested Block Content The `InnerBlocks` component allows blocks to contain other blocks as children, enabling complex nested layouts like columns, groups, and custom containers. Configure allowed blocks, templates, and orientation for complete control over nested content. ```jsx import { registerBlockType } from '@wordpress/blocks'; import { useBlockProps, InnerBlocks } from '@wordpress/block-editor'; import { __ } from '@wordpress/i18n'; // Define a template for initial inner blocks const INNER_BLOCKS_TEMPLATE = [ [ 'core/heading', { level: 2, placeholder: 'Section Title' } ], [ 'core/paragraph', { placeholder: 'Add your content here...' } ], ]; // Restrict which blocks can be inserted const ALLOWED_BLOCKS = [ 'core/heading', 'core/paragraph', 'core/image', 'core/list', 'core/buttons', ]; registerBlockType( 'my-plugin/content-section', { apiVersion: 3, title: __( 'Content Section' ), category: 'layout', icon: 'layout', attributes: { sectionId: { type: 'string', }, }, supports: { align: [ 'wide', 'full' ], html: false, }, edit: ( { attributes, setAttributes } ) => { const blockProps = useBlockProps( { className: 'content-section', } ); return ( <section { ...blockProps } id={ attributes.sectionId }> <InnerBlocks allowedBlocks={ ALLOWED_BLOCKS } template={ INNER_BLOCKS_TEMPLATE } templateLock={ false } // false | 'all' | 'insert' | 'contentOnly' orientation="vertical" // 'vertical' | 'horizontal' renderAppender={ InnerBlocks.ButtonBlockAppender } /> </section> ); }, save: ( { attributes } ) => { const blockProps = useBlockProps.save( { className: 'content-section', } ); return ( <section { ...blockProps } id={ attributes.sectionId }> <InnerBlocks.Content /> </section> ); }, } ); // Example: Two-column layout with locked template registerBlockType( 'my-plugin/two-columns', { apiVersion: 3, title: __( 'Two Columns' ), category: 'layout', icon: 'columns', edit: () => { const blockProps = useBlockProps( { className: 'two-columns' } ); const COLUMNS_TEMPLATE = [ [ 'core/column', {}, [ [ 'core/paragraph', { placeholder: 'Left column content...' } ], ] ], [ 'core/column', {}, [ [ 'core/paragraph', { placeholder: 'Right column content...' } ], ] ], ]; return ( <div { ...blockProps }> <InnerBlocks allowedBlocks={ [ 'core/column' ] } template={ COLUMNS_TEMPLATE } templateLock="all" // Lock structure, prevent adding/removing /> </div> ); }, save: () => { const blockProps = useBlockProps.save( { className: 'two-columns' } ); return ( <div { ...blockProps }> <InnerBlocks.Content /> </div> ); }, } ); ``` --- ## Dynamic Blocks with Server-Side Rendering Dynamic blocks render their content on the server using PHP, enabling blocks that display live data (like latest posts) or need to change without re-saving. Define a `render_callback` in PHP that receives block attributes and returns HTML. ```jsx // JavaScript: edit.js import { registerBlockType } from '@wordpress/blocks'; import { useSelect } from '@wordpress/data'; import { useBlockProps, InspectorControls } from '@wordpress/block-editor'; import { PanelBody, RangeControl, SelectControl, Spinner } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; registerBlockType( 'my-plugin/recent-posts', { apiVersion: 3, title: __( 'Recent Posts' ), category: 'widgets', icon: 'list-view', attributes: { numberOfPosts: { type: 'number', default: 5, }, postType: { type: 'string', default: 'post', }, displayFeaturedImage: { type: 'boolean', default: true, }, }, edit: ( { attributes, setAttributes } ) => { const { numberOfPosts, postType, displayFeaturedImage } = attributes; const blockProps = useBlockProps(); // Fetch posts for preview in editor const { posts, isLoading } = useSelect( ( select ) => { const { getEntityRecords, hasFinishedResolution } = select( 'core' ); const query = { per_page: numberOfPosts, _embed: displayFeaturedImage, }; return { posts: getEntityRecords( 'postType', postType, query ), isLoading: ! hasFinishedResolution( 'getEntityRecords', [ 'postType', postType, query ] ), }; }, [ numberOfPosts, postType, displayFeaturedImage ] ); return ( <> <InspectorControls> <PanelBody title={ __( 'Settings' ) }> <SelectControl label={ __( 'Post Type' ) } value={ postType } options={ [ { label: 'Posts', value: 'post' }, { label: 'Pages', value: 'page' }, ] } onChange={ ( value ) => setAttributes( { postType: value } ) } /> <RangeControl label={ __( 'Number of Posts' ) } value={ numberOfPosts } onChange={ ( value ) => setAttributes( { numberOfPosts: value } ) } min={ 1 } max={ 20 } /> </PanelBody> </InspectorControls> <div { ...blockProps }> { isLoading && <Spinner /> } { ! isLoading && posts?.length === 0 && <p>No posts found.</p> } { ! isLoading && posts?.length > 0 && ( <ul className="recent-posts-list"> { posts.map( ( post ) => ( <li key={ post.id }> <a href={ post.link }>{ post.title.rendered }</a> </li> ) ) } </ul> ) } </div> </> ); }, // Return null for dynamic blocks - rendering happens server-side save: () => null, } ); ``` ```php <?php // PHP: render.php or in plugin file function my_plugin_render_recent_posts( $attributes ) { $args = array( 'post_type' => $attributes['postType'] ?? 'post', 'posts_per_page' => $attributes['numberOfPosts'] ?? 5, 'post_status' => 'publish', ); $posts = get_posts( $args ); if ( empty( $posts ) ) { return '<p>' . esc_html__( 'No posts found.', 'my-plugin' ) . '</p>'; } $output = '<ul class="wp-block-my-plugin-recent-posts">'; foreach ( $posts as $post ) { $output .= sprintf( '<li><a href="%s">%s</a></li>', esc_url( get_permalink( $post ) ), esc_html( get_the_title( $post ) ) ); } $output .= '</ul>'; return $output; } function my_plugin_register_recent_posts_block() { register_block_type( __DIR__ . '/build/recent-posts', array( 'render_callback' => 'my_plugin_render_recent_posts', ) ); } add_action( 'init', 'my_plugin_register_recent_posts_block' ); ``` --- ## BlockEditorProvider - Custom Block Editor The `BlockEditorProvider` component creates a standalone block editor instance that can be embedded anywhere in WordPress or used outside of WordPress entirely. Combine with `BlockCanvas` and other block editor components to build custom editing experiences. ```jsx import { useState, useMemo } from '@wordpress/element'; import { BlockEditorProvider, BlockCanvas, BlockInspector, BlockToolbar, BlockList, WritingFlow, ObserveTyping, } from '@wordpress/block-editor'; import { registerCoreBlocks } from '@wordpress/block-library'; import { Popover, SlotFillProvider } from '@wordpress/components'; import { parse, serialize } from '@wordpress/blocks'; // Import required styles import '@wordpress/components/build-style/style.css'; import '@wordpress/block-editor/build-style/style.css'; import '@wordpress/block-library/build-style/style.css'; import '@wordpress/block-library/build-style/editor.css'; // Register core blocks once registerCoreBlocks(); function CustomBlockEditor( { initialContent = '', onSave } ) { // Parse initial content into blocks const initialBlocks = useMemo( () => parse( initialContent ), [ initialContent ] ); const [ blocks, setBlocks ] = useState( initialBlocks ); // Settings for the block editor const settings = useMemo( () => ( { hasFixedToolbar: true, focusMode: false, styles: [], // Limit available blocks allowedBlockTypes: [ 'core/paragraph', 'core/heading', 'core/image', 'core/list', 'core/quote', ], } ), [] ); const handleSave = () => { const content = serialize( blocks ); onSave?.( content ); }; return ( <SlotFillProvider> <div className="custom-block-editor"> <BlockEditorProvider value={ blocks } onInput={ setBlocks } onChange={ setBlocks } settings={ settings } > <div className="editor-header"> <BlockToolbar /> <button onClick={ handleSave } className="save-button"> Save Content </button> </div> <div className="editor-main"> <div className="editor-content"> <BlockCanvas height="500px" /> </div> <div className="editor-sidebar"> <BlockInspector /> </div> </div> <Popover.Slot /> </BlockEditorProvider> </div> </SlotFillProvider> ); } // Usage function App() { const handleSave = ( content ) => { console.log( 'Saved content:', content ); // Send to server, update state, etc. }; return ( <CustomBlockEditor initialContent="<!-- wp:paragraph --><p>Hello World</p><!-- /wp:paragraph -->" onSave={ handleSave } /> ); } ``` --- ## Block Variations - Create Block Presets Block variations allow you to create pre-configured versions of existing blocks with different default attributes, inner blocks, or visual styles. Variations appear as separate options in the block inserter. ```jsx import { registerBlockVariation, unregisterBlockVariation } from '@wordpress/blocks'; import { __ } from '@wordpress/i18n'; // Register variations for the Group block registerBlockVariation( 'core/group', { name: 'card', title: __( 'Card' ), description: __( 'A contained card with shadow and padding.' ), icon: 'index-card', attributes: { style: { spacing: { padding: { top: '24px', right: '24px', bottom: '24px', left: '24px', }, }, border: { radius: '8px', }, boxShadow: '0 4px 6px rgba(0, 0, 0, 0.1)', }, backgroundColor: 'white', }, isActive: ( blockAttributes, variationAttributes ) => { return blockAttributes.backgroundColor === variationAttributes.backgroundColor; }, scope: [ 'inserter', 'transform' ], } ); // Register variations for the Columns block registerBlockVariation( 'core/columns', { name: 'two-columns-equal', title: __( 'Two Equal Columns' ), icon: 'columns', innerBlocks: [ [ 'core/column', { width: '50%' } ], [ 'core/column', { width: '50%' } ], ], scope: [ 'block' ], } ); registerBlockVariation( 'core/columns', { name: 'sidebar-content', title: __( 'Sidebar + Content' ), icon: 'align-pull-left', innerBlocks: [ [ 'core/column', { width: '33.33%' } ], [ 'core/column', { width: '66.66%' } ], ], scope: [ 'block' ], } ); // Register variation for Embed block registerBlockVariation( 'core/embed', { name: 'my-custom-embed', title: __( 'My Platform' ), icon: 'video-alt3', attributes: { providerNameSlug: 'my-platform', }, isActive: [ 'providerNameSlug' ], // Use array for simple attribute matching } ); // Programmatic variation registration with PHP file (block.json) // In block.json: "variations": "file:./variations.php" ``` ```php <?php // variations.php - Server-side variations return array( array( 'name' => 'featured-post', 'title' => __( 'Featured Post', 'my-plugin' ), 'icon' => 'star-filled', 'attributes' => array( 'postType' => 'post', 'featured' => true, ), 'isActive' => array( 'featured' ), ), array( 'name' => 'featured-page', 'title' => __( 'Featured Page', 'my-plugin' ), 'icon' => 'admin-page', 'attributes' => array( 'postType' => 'page', 'featured' => true, ), 'isActive' => array( 'postType', 'featured' ), ), ); ``` --- ## Block Styles - Visual Style Variations Block styles add CSS class-based visual variations to blocks without changing their structure or attributes. Users can switch between styles from the block toolbar or inspector. ```jsx import { registerBlockStyle, unregisterBlockStyle } from '@wordpress/blocks'; import { __ } from '@wordpress/i18n'; // Register styles for core blocks registerBlockStyle( 'core/button', { name: 'gradient', label: __( 'Gradient' ), } ); registerBlockStyle( 'core/button', { name: 'outline-thick', label: __( 'Thick Outline' ), } ); registerBlockStyle( 'core/image', { name: 'rounded', label: __( 'Rounded' ), isDefault: false, } ); registerBlockStyle( 'core/image', { name: 'shadow', label: __( 'Shadow' ), } ); registerBlockStyle( 'core/quote', { name: 'fancy', label: __( 'Fancy Quote' ), } ); // Register multiple styles for a block at once [ 'core/paragraph', 'core/heading' ].forEach( ( blockName ) => { registerBlockStyle( blockName, { name: 'highlight', label: __( 'Highlight' ), } ); } ); // Unregister a built-in style unregisterBlockStyle( 'core/quote', 'plain' ); ``` ```css /* style.css - CSS for block styles */ /* Button: Gradient style */ .wp-block-button.is-style-gradient .wp-block-button__link { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border: none; color: white; } /* Button: Thick Outline style */ .wp-block-button.is-style-outline-thick .wp-block-button__link { background: transparent; border: 3px solid currentColor; } /* Image: Rounded style */ .wp-block-image.is-style-rounded img { border-radius: 50%; } /* Image: Shadow style */ .wp-block-image.is-style-shadow img { box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2); } /* Quote: Fancy style */ .wp-block-quote.is-style-fancy { border-left: 4px solid #764ba2; padding-left: 1.5em; font-style: italic; position: relative; } .wp-block-quote.is-style-fancy::before { content: '\201C'; font-size: 4em; position: absolute; left: -0.5em; top: -0.25em; color: #764ba2; opacity: 0.3; } /* Paragraph/Heading: Highlight style */ .is-style-highlight { background: linear-gradient(180deg, transparent 60%, #ffeb3b 60%); display: inline; } ``` --- ## Summary Gutenberg provides a comprehensive framework for building modern WordPress experiences through its block-based architecture. The key integration patterns include: using `registerBlockType` with `block.json` for defining custom blocks; leveraging `useSelect` and `useDispatch` hooks for reactive state management; utilizing `useEntityRecord` and `useEntityRecords` for WordPress data operations; and implementing `InnerBlocks` for composable nested structures. The `@wordpress/api-fetch` utility simplifies REST API interactions while the data layer's store system enables complex state management with automatic async resolution. For developers building WordPress solutions, the recommended approach is to start with `@wordpress/create-block` to scaffold new blocks, use `block.json` for metadata-driven registration, implement server-side rendering for dynamic content, and leverage the component library from `@wordpress/components` for consistent UI patterns. The modular package architecture means you can import only what you need, keeping bundle sizes optimized while accessing the full power of the WordPress editing experience.