This document provides comprehensive rules for creating WordPress Gutenberg blocks in the Example project.
Every Gutenberg block follows this basic structure:
import { registerBlockType } from '@wordpress/blocks';
import { InspectorControls, RichText } from '@wordpress/block-editor';
import { CheckboxControl, PanelBody, TextControl } from '@wordpress/components';
import { CustomMediaUpload } from '../../library/custom-media-upload.js';
registerBlockType('example/block-name', {
title: 'Example / Category / Block Name',
description: 'Block description',
category: 'example',
attributes: {
// attributes definition
},
edit({attributes, setAttributes}) {
// edit method implementation
},
save({attributes}) {
// save method implementation
},
});- Block ID: Use format
example/block-name(kebab-case) - Title: Use format
Example / Category / Block Name(Title Case) - File Name: Use kebab-case matching the block name (e.g.,
hero.js,text-section.js)
Every attribute must have these three fields:
type: Data type (string, text, boolean, number, array, object)source: How to extract data (text, attribute, html, children)selector: CSS selector to target the element
attribute: Required when source is "attribute" - specifies which HTML attribute to readdefault: Default value for the attribute
textAttribute: {
type: "text",
source: "text",
selector: "h1", // or appropriate selector
}richTextAttribute: {
type: "string",
source: "html",
selector: "p", // or appropriate selector
}imageAttribute: {
type: "string",
source: "attribute",
attribute: "src",
selector: "img",
}booleanAttribute: {
type: "boolean",
source: "attribute",
selector: "[data-attribute-name]",
attribute: "data-attribute-name",
default: false,
}linkHref: {
type: "string",
source: "attribute",
selector: "a",
attribute: "href",
},
linkText: {
type: "string",
source: "text",
selector: "a",
}Always import necessary components at the top:
import { InspectorControls, RichText } from '@wordpress/block-editor';
import { CheckboxControl, PanelBody, TextControl } from '@wordpress/components';
import { CustomMediaUpload } from '../../library/custom-media-upload.js';The edit method should return JSX with this structure:
edit({attributes, setAttributes}) {
return (
<>
<InspectorControls>
<PanelBody>
{/* Control components here */}
</PanelBody>
</InspectorControls>
{/* Block preview HTML here */}
</>
)
}<CustomMediaUpload
mediaUrl={attributes.imageAttribute}
onMediaSelected={(url) => { setAttributes({imageAttribute: url}) }}
/><TextControl
label="Label Text"
value={attributes.textAttribute}
onChange={(value) => {setAttributes({textAttribute: value})}}
/><CheckboxControl
label="Checkbox Label"
value={attributes.booleanAttribute}
checked={attributes.booleanAttribute}
onChange={(value) => { setAttributes({booleanAttribute: value}) }}
/><RichText
value={attributes.richTextAttribute}
onChange={(value) => {setAttributes({richTextAttribute: value})}}
placeholder='Placeholder text'
tagName='h1'
className="css-classes"
/>The save method should return the final HTML output without any WordPress components. It's critical to use the correct method for rendering attribute values to avoid issues with escaped HTML.
You can directly render the attribute value inside the HTML tag.
save({attributes}) {
return (
<section className="css-classes">
<h1>{attributes.textAttribute}</h1>
<img src={attributes.imageAttribute} alt="" />
{attributes.booleanAttribute && (
<a href={attributes.linkHref}>{attributes.linkText}</a>
)}
</section>
);
}When an attribute uses source: "html", you must use the RichText.Content component to render the value in the save function. This prevents the HTML content from being escaped and preserves formatting like bold, italics, or links.
Important: You also need to import RichText from @wordpress/block-editor in your block's file if it's not already there for the edit function.
import { RichText } from '@wordpress/block-editor'; // Ensure this import exists
//...
save({ attributes }) {
return (
<div>
<RichText.Content
tagName="p"
className="my-paragraph-class"
value={attributes.richTextAttribute}
/>
</div>
);
}- Use Tailwind CSS classes for styling
- Follow the existing project's design system
- Use semantic HTML elements
- Include responsive classes (sm:, md:, lg:, xl:)
Use data attributes for conditional rendering:
// In both edit and save methods
<section data-show-element={attributes.booleanAttribute}>- Place block files in:
wp/wp-content/plugins/example-blocks/src/blocks/ - Use appropriate subdirectories:
index/for main blocks,layout/for layout blocks - Import blocks in the main index.js file
- Always use the CustomMediaUpload component for image uploads instead of the default MediaUpload
- Keep edit and save methods in sync - the HTML structure should match
- Use semantic HTML elements in both edit and save methods
- Include proper accessibility attributes (alt text, aria labels)
- Test conditional rendering in both edit and save methods
- Use consistent naming for attributes and CSS classes
- Add meaningful descriptions to block registration
- Group related controls in InspectorControls PanelBody
- Provide placeholder text for RichText components
- Use default values for boolean attributes
Refer to hero.js for a complete implementation example that demonstrates:
- Multiple attribute types
- Image upload with CustomMediaUpload
- Conditional rendering
- RichText usage
- Complex HTML structure with Tailwind CSS
- Proper InspectorControls setup
When the HTML markup contains multiple elements of the same type (e.g., multiple images, headings, or paragraphs), you must assign unique CSS classes to differentiate them in the selector:
// For first image
decorativeImage: {
type: "string",
source: "attribute",
attribute: "src",
selector: ".decorative-image",
},
// For second image
mainImage: {
type: "string",
source: "attribute",
attribute: "src",
selector: ".main-image",
}// For main title
mainTitle: {
type: "string",
source: "html",
selector: ".main-title",
},
// For content title
contentTitle: {
type: "string",
source: "html",
selector: ".content-title",
}// For main description
mainDescription: {
type: "string",
source: "html",
selector: ".main-description",
},
// For guide descriptions
guideDescription1: {
type: "string",
source: "html",
selector: ".guide-description-1",
},
guideDescription2: {
type: "string",
source: "html",
selector: ".guide-description-2",
}// For first tag
tag1: {
type: "string",
source: "text",
selector: ".tag-1",
},
// For second tag
tag2: {
type: "string",
source: "text",
selector: ".tag-2",
}{attributes.showLink && (
<a href={attributes.linkHref}>{attributes.linkText}</a>
)}{attributes.imageUrl && (
<img src={attributes.imageUrl} alt="" />
)}<RichText
value={attributes.title}
onChange={(value) => setAttributes({title: value})}
tagName="h2"
placeholder="Enter title"
/>
<RichText
value={attributes.content}
onChange={(value) => setAttributes({content: value})}
tagName="p"
placeholder="Enter content"
/>This rule set ensures consistent, maintainable, and properly structured Gutenberg blocks for the Example project.
For
<CustomMediaUpload>see: https://gist.github.com/anova/50e7c97815332bb4301e60507f1e21b2