Tutorials / WordPress

WordPress Custom Block Development - Complete Guide

Mahesh Mahesh Waghmare
5 min read Intermediate

WordPress Gutenberg blocks have revolutionized how we build WordPress sites. Creating custom blocks allows you to extend WordPress functionality and create unique content experiences. This comprehensive guide will walk you through building custom blocks from scratch.

Introduction to Custom Blocks

Gutenberg blocks are the building blocks of modern WordPress content. Custom blocks let you create reusable components that match your specific needs.

Why Create Custom Blocks?

  • Reusable content components
  • Consistent design patterns
  • Enhanced user experience
  • Better content management
  • Modern development workflow

Block Types:

  • Static blocks - Fixed content structure
  • Dynamic blocks - Server-rendered content
  • Nested blocks - Blocks within blocks

Prerequisites

Before starting, ensure you have:

  • WordPress 5.0+ installed
  • Node.js and npm installed
  • Basic knowledge of JavaScript (ES6+)
  • Understanding of React (blocks use React)
  • Code editor (VS Code recommended)
  • Local development environment

Required Tools:

  • @wordpress/scripts - Build tooling
  • @wordpress/block-editor - Block editor components
  • @wordpress/components - UI components

Understanding Block Structure

A custom block consists of:

1. Block Registration

  • Block name and namespace
  • Block configuration
  • Block attributes

2. Edit Component

  • What editors see in the editor
  • Interactive editing interface
  • Uses React components

3. Save Function

  • What gets saved to the database
  • HTML output for frontend
  • Can be static or dynamic

4. Block Styles

  • Editor styles
  • Frontend styles
  • Responsive design
Advertisement

Creating Your First Block

Step 1: Setup Project Structure

Create your block plugin structure:

my-custom-blocks/
├── package.json
├── src/
│   ├── blocks/
│   │   └── my-first-block/
│   │       ├── index.js
│   │       ├── edit.js
│   │       ├── save.js
│   │       └── style.css
│   └── index.js
└── build/

Step 2: Install Dependencies

npm install @wordpress/scripts --save-dev
npm install @wordpress/block-editor @wordpress/components --save

Step 3: Configure package.json

{
  "scripts": {
    "build": "wp-scripts build",
    "start": "wp-scripts start"
  }
}

Step 4: Register Your Block

src/blocks/my-first-block/index.js:

import { registerBlockType } from '@wordpress/blocks';
import Edit from './edit';
import save from './save';

registerBlockType('my-plugin/my-first-block', {
	title: 'My First Block',
	icon: 'smiley',
	category: 'common',
	attributes: {
		content: {
			type: 'string',
			source: 'html',
			selector: 'p',
		},
	},
	edit: Edit,
	save,
});

Step 5: Create Edit Component

src/blocks/my-first-block/edit.js:

import { useBlockProps, RichText } from '@wordpress/block-editor';
import { __ } from '@wordpress/i18n';

export default function Edit({ attributes, setAttributes }) {
	const { content } = attributes;
	const blockProps = useBlockProps();

	return (
		<div {...blockProps}>
			<RichText
				tagName="p"
				value={content}
				onChange={(value) => setAttributes({ content: value })}
				placeholder={__('Enter content...', 'my-plugin')}
			/>
		</div>
	);
}

Step 6: Create Save Function

src/blocks/my-first-block/save.js:

import { useBlockProps, RichText } from '@wordpress/block-editor';

export default function save({ attributes }) {
	const { content } = attributes;
	const blockProps = useBlockProps.save();

	return (
		<div {...blockProps}>
			<RichText.Content tagName="p" value={content} />
		</div>
	);
}

Working with Block Attributes

Attributes define the data structure of your block:

Common Attribute Types:

  • string - Text content
  • number - Numeric values
  • boolean - True/false values
  • array - Lists of items
  • object - Complex data structures

Example with Multiple Attributes:

attributes: {
	title: {
		type: 'string',
		source: 'html',
		selector: 'h2',
	},
	count: {
		type: 'number',
		default: 0,
	},
	isActive: {
		type: 'boolean',
		default: true,
	},
	items: {
		type: 'array',
		default: [],
	},
},

Edit and Save Functions

Advanced Edit Component

import { 
	useBlockProps, 
	RichText, 
	InspectorControls 
} from '@wordpress/block-editor';
import { PanelBody, RangeControl } from '@wordpress/components';

export default function Edit({ attributes, setAttributes }) {
	const { title, count } = attributes;
	const blockProps = useBlockProps();

	return (
		<>
			<InspectorControls>
				<PanelBody title="Settings">
					<RangeControl
						label="Count"
						value={count}
						onChange={(value) => setAttributes({ count: value })}
						min={0}
						max={100}
					/>
				</PanelBody>
			</InspectorControls>
			<div {...blockProps}>
				<RichText
					tagName="h2"
					value={title}
					onChange={(value) => setAttributes({ title: value })}
					placeholder="Enter title..."
				/>
				<p>Count: {count}</p>
			</div>
		</>
	);
}

Dynamic Save Function

For server-rendered content:

export default function save() {
	return null;
}

Then register as dynamic block in PHP:

register_block_type('my-plugin/my-first-block', [
	'render_callback' => 'render_my_block',
]);

function render_my_block($attributes) {
	ob_start();
	// Render your block HTML
	return ob_get_clean();
}
Advertisement

Advanced Block Patterns

Nested Blocks

Allow child blocks within your block:

registerBlockType('my-plugin/container-block', {
	// ... other config
	supports: {
		innerBlocks: true,
	},
	edit: ({ innerBlocks, setInnerBlocks }) => {
		return (
			<div {...useBlockProps()}>
				<InnerBlocks />
			</div>
		);
	},
	save: () => {
		return (
			<div {...useBlockProps.save()}>
				<InnerBlocks.Content />
			</div>
		);
	},
});

Block Variations

Create variations of the same block:

registerBlockType('my-plugin/button-block', {
	// ... base config
	variations: [
		{
			name: 'primary',
			title: 'Primary Button',
			attributes: { variant: 'primary' },
		},
		{
			name: 'secondary',
			title: 'Secondary Button',
			attributes: { variant: 'secondary' },
		},
	],
});

Best Practices

  • Use semantic HTML in save function
  • Implement proper accessibility (ARIA labels)
  • Optimize block performance
  • Follow WordPress coding standards
  • Test blocks thoroughly before release
  • Document block attributes and usage
  • Provide default values for attributes
  • Use WordPress i18n for translations
  • Handle edge cases gracefully
  • Keep edit and save functions in sync

Conclusion

Creating custom WordPress blocks opens up endless possibilities for content creation. Key takeaways:

  • Blocks use React for the editor interface
  • Attributes define block data structure
  • Edit function controls editor experience
  • Save function determines frontend output
  • Follow WordPress best practices

Start with simple blocks and gradually add complexity. The WordPress block API is powerful and flexible, allowing you to create exactly what you need for your projects.

Advertisement
Mahesh Waghmare

Written by Mahesh Waghmare

I bridge the gap between WordPress architecture and modern React frontends. Currently building tools for the AI era.

Follow on Twitter