All skills
Skillintermediate

Gutenberg Blocks

WordPress 6.4+ uses the Block Editor (Gutenberg) as the primary editing experience. Blocks are the fundamental building units.

Claude Code Knowledge Pack7/10/2026

Overview

Gutenberg Blocks


Block Development Overview

WordPress 6.4+ uses the Block Editor (Gutenberg) as the primary editing experience. Blocks are the fundamental building units.

Block Types

TypeDescriptionUse Case
StaticFixed HTML outputSimple content, images
DynamicServer-renderedPosts list, dynamic data
InteractiveClient-side JSAccordions, tabs, carousels

Project Setup

Using @wordpress/create-block

# Create a new block plugin
npx @wordpress/create-block my-block --namespace my-plugin

# Create with specific template
npx @wordpress/create-block my-block --template @wordpress/create-block-interactive-template

# Create dynamic block
npx @wordpress/create-block my-block --variant dynamic

Generated Structure

my-block/
├── my-block.php           # Plugin file
├── package.json           # NPM dependencies
├── src/
│   ├── block.json         # Block metadata
│   ├── edit.js            # Editor component
│   ├── save.js            # Frontend save
│   ├── index.js           # Block registration
│   ├── editor.scss        # Editor styles
│   └── style.scss         # Frontend styles
├── build/                 # Compiled assets
└── readme.txt

package.json Scripts

{
    "name": "my-block",
    "version": "1.0.0",
    "scripts": {
        "build": "wp-scripts build",
        "start": "wp-scripts start",
        "format": "wp-scripts format",
        "lint:js": "wp-scripts lint-js",
        "lint:css": "wp-scripts lint-style",
        "packages-update": "wp-scripts packages-update"
    },
    "devDependencies": {
        "@wordpress/scripts": "^27.0.0"
    }
}

Block Registration

block.json (WordPress 6.4+)

{
    "$schema": "https://schemas.wp.org/trunk/block.json",
    "apiVersion": 3,
    "name": "my-plugin/my-block",
    "version": "1.0.0",
    "title": "My Block",
    "category": "widgets",
    "icon": "smiley",
    "description": "A custom block for displaying content.",
    "keywords": ["custom", "content", "block"],
    "supports": {
        "html": false,
        "align": ["wide", "full"],
        "anchor": true,
        "color": {
            "background": true,
            "text": true,
            "link": true,
            "gradients": true
        },
        "spacing": {
            "margin": true,
            "padding": true,
            "blockGap": true
        },
        "typography": {
            "fontSize": true,
            "lineHeight": true
        },
        "__experimentalBorder": {
            "color": true,
            "radius": true,
            "style": true,
            "width": true
        }
    },
    "attributes": {
        "content": {
            "type": "string",
            "source": "html",
            "selector": "p"
        },
        "alignment": {
            "type": "string",
            "default": "left"
        },
        "showBorder": {
            "type": "boolean",
            "default": false
        },
        "items": {
            "type": "array",
            "default": []
        },
        "selectedPostId": {
            "type": "number"
        }
    },
    "example": {
        "attributes": {
            "content": "Example content for the block preview."
        }
    },
    "textdomain": "my-plugin",
    "editorScript": "file:./index.js",
    "editorStyle": "file:./index.css",
    "style": "file:./style-index.css",
    "viewScript": "file:./view.js",
    "render": "file:./render.php"
}

PHP Registration

<?php
declare(strict_types=1);

/**
 * Register all blocks
 */
function my_plugin_register_blocks(): void {
    // Auto-register from block.json
    register_block_type(__DIR__ . '/build/my-block');

    // Or with additional arguments
    register_block_type(__DIR__ . '/build/another-block', [
        'render_callback' => 'my_plugin_render_another_block',
    ]);
}
add_action('init', 'my_plugin_register_blocks');

/**
 * Register block category
 */
function my_plugin_block_categories(array $categories): array {
    return array_merge(
        [
            [
                'slug'  => 'my-plugin-blocks',
                'title' => __('My Plugin Blocks', 'my-plugin'),
                'icon'  => 'wordpress',
            ],
        ],
        $categories
    );
}
add_filter('block_categories_all', 'my_plugin_block_categories');

Static Block Development

index.js (Entry Point)

/**
 * WordPress dependencies
 */

/**
 * Internal dependencies
 */

/**
 * Register block
 */
registerBlockType(metadata.name, {
    edit: Edit,
    save,
});

edit.js (Editor Component)

/**
 * WordPress dependencies
 */

    useBlockProps,
    RichText,
    InspectorControls,
    BlockControls,
    AlignmentToolbar,
    MediaUpload,
    MediaUploadCheck,
} from '@wordpress/block-editor';

    PanelBody,
    ToggleControl,
    TextControl,
    SelectControl,
    RangeControl,
    Button,
} from '@wordpress/components';

/**
 * Edit component
 *
 * @param {Object} props               Block props
 * @param {Object} props.attributes    Block attributes
 * @param {Function} props.setAttributes Function to update attributes
 * @return {JSX.Element} Block edit component
 */

    const { content, alignment, showBorder, imageId, imageUrl, columns } = attributes;

    const blockProps = useBlockProps({
        className: `align-${alignment}${showBorder ? ' has-border' : ''}`,
    });

    const onSelectImage = (media) => {
        setAttributes({
            imageId: media.id,
            imageUrl: media.url,
        });
    };

    const onRemoveImage = () => {
        setAttributes({
            imageId: undefined,
            imageUrl: undefined,
        });
    };

    return (
        <>
            
                
                        setAttributes({ alignment: newAlignment })
                    }
                />
            

            
                
                     setAttributes({ showBorder: value })}
                    />

                     setAttributes({ columns: value })}
                        min={1}
                        max={6}
                    />

                     setAttributes({ layout: value })}
                    />
                

                
                    
                         (
                                <div className="editor-post-featured-image">
                                    {imageUrl ? (
                                        <>
                                            <img src={imageUrl} alt="" />
                                            
                                                {__('Remove Image', 'my-plugin')}
                                            
                                        </>
                                    ) : (
                                        
                                            {__('Select Image', 'my-plugin')}
                                        
                                    )}
                                </div>
                            )}
                        />
                    
                
            

            <div {...blockProps}>
                {imageUrl && (
                    <img src={imageUrl} alt="" className="block-image" />
                )}
                 setAttributes({ content: value })}
                    placeholder={__('Enter content...', 'my-plugin')}
                    allowedFormats={['core/bold', 'core/italic', 'core/link']}
                />
            </div>
        </>
    );
}

save.js (Frontend Output)

/**
 * WordPress dependencies
 */

/**
 * Save component
 *
 * @param {Object} props            Block props
 * @param {Object} props.attributes Block attributes
 * @return {JSX.Element} Block save component
 */

    const { content, alignment, showBorder, imageUrl } = attributes;

    const blockProps = useBlockProps.save({
        className: `align-${alignment}${showBorder ? ' has-border' : ''}`,
    });

    return (
        <div {...blockProps}>
            {imageUrl && (
                <img src={imageUrl} alt="" className="block-image" />
            )}
            <RichText.Content tagName="p" value={content} />
        </div>
    );
}

Dynamic Block Development

Dynamic blocks render on the server, useful for content that changes or requires PHP logic.

block.json for Dynamic Block

{
    "$schema": "https://schemas.wp.org/trunk/block.json",
    "apiVersion": 3,
    "name": "my-plugin/recent-posts",
    "title": "Recent Posts",
    "category": "widgets",
    "icon": "list-view",
    "description": "Display recent posts with customizable options.",
    "supports": {
        "html": false,
        "align": ["wide", "full"]
    },
    "attributes": {
        "numberOfPosts": {
            "type": "number",
            "default": 5
        },
        "postType": {
            "type": "string",
            "default": "post"
        },
        "showExcerpt": {
            "type": "boolean",
            "default": true
        },
        "showFeaturedImage": {
            "type": "boolean",
            "default": true
        },
        "categories": {
            "type": "array",
            "default": []
        }
    },
    "textdomain": "my-plugin",
    "editorScript": "file:./index.js",
    "style": "file:./style-index.css",
    "render": "file:./render.php"
}

edit.js for Dynamic Block

/**
 * WordPress dependencies
 */

    PanelBody,
    RangeControl,
    ToggleControl,
    SelectControl,
    Spinner,
} from '@wordpress/components';

/**
 * Edit component for dynamic block
 */

    const { numberOfPosts, postType, showExcerpt, showFeaturedImage } = attributes;

    const blockProps = useBlockProps();

    // Fetch post types for select
    const postTypes = useSelect((select) => {
        const { getPostTypes } = select(coreStore);
        const types = getPostTypes({ per_page: -1 });
        return types?.filter((type) => type.viewable && type.rest_base) || [];
    }, []);

    // Fetch categories
    const categories = useSelect((select) => {
        const { getEntityRecords } = select(coreStore);
        return getEntityRecords('taxonomy', 'category', { per_page: -1 }) || [];
    }, []);

    const postTypeOptions = postTypes.map((type) => ({
        label: type.labels.singular_name,
        value: type.slug,
    }));

    return (
        <>
            
                
                     setAttributes({ numberOfPosts: value })}
                        min={1}
                        max={20}
                    />

                    {postTypeOptions.length > 0 && (
                         setAttributes({ postType: value })}
                        />
                    )}

                     setAttributes({ showExcerpt: value })}
                    />

                     setAttributes({ showFeaturedImage: value })}
                    />
                
            

            <div {...blockProps}>
                 (
                        <div className="loading-placeholder">
                            
                            <p>{__('Loading...', 'my-plugin')}</p>
                        </div>
                    )}
                    EmptyResponsePlaceholder={() => (