Read More
Building Custom Gutenberg Blocks for Enterprise WordPress Sites
Building Custom Gutenberg Blocks for Enterprise WordPress Sites
Custom Gutenberg blocks are essential for enterprise WordPress sites. They ensure brand consistency, improve content creation efficiency, and provide powerful customization options.
Why Custom Blocks Matter
Enterprise sites need:
- Brand consistency across all content
- Streamlined workflows for content creators
- Advanced functionality beyond default blocks
- Scalable solutions for multiple sites
Development Environment Setup
Required Tools
Install WordPress CLI
curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.pharCreate block plugin
wp scaffold plugin my-custom-blocksInstall dependencies
npm install @wordpress/scripts --save-dev
Project Structure
my-custom-blocks/
├── src/
│ ├── blocks/
│ │ ├── hero-section/
│ │ ├── testimonial-slider/
│ │ └── cta-banner/
│ └── components/
├── build/
├── includes/
└── my-custom-blocks.php
Block Development
1. Hero Section Block
// src/blocks/hero-section/index.js
import { registerBlockType } from '@wordpress/blocks';
import { useBlockProps, RichText, MediaUpload, InspectorControls } from '@wordpress/block-editor';
import { PanelBody, Button, ToggleControl } from '@wordpress/components';registerBlockType('my-blocks/hero-section', {
title: 'Hero Section',
description: 'A customizable hero section with background image and content.',
category: 'design',
icon: 'cover-image',
attributes: {
title: {
type: 'string',
source: 'html',
selector: 'h1',
},
subtitle: {
type: 'string',
source: 'html',
selector: '.subtitle',
},
backgroundImage: {
type: 'string',
default: '',
},
overlayOpacity: {
type: 'number',
default: 0.5,
},
showCTA: {
type: 'boolean',
default: true,
},
ctaText: {
type: 'string',
default: 'Get Started',
},
ctaUrl: {
type: 'string',
default: '',
},
},
edit: (props) => {
const { attributes, setAttributes } = props;
const { title, subtitle, backgroundImage, overlayOpacity, showCTA, ctaText, ctaUrl } = attributes;
const blockProps = useBlockProps();
return (
label="Show Call to Action"
checked={showCTA}
onChange={(value) => setAttributes({ showCTA: value })}
/>
label="Overlay Opacity"
value={overlayOpacity}
onChange={(value) => setAttributes({ overlayOpacity: value })}
min={0}
max={1}
step={0.1}
/>
backgroundImage: backgroundImage ? url(${backgroundImage})
: 'none',
position: 'relative',
minHeight: '500px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}>
{backgroundImage && (
className="hero-overlay"
style={{
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: rgba(0, 0, 0, ${overlayOpacity})
,
}}
/>
)}
onSelect={(media) => setAttributes({ backgroundImage: media.url })}
type="image"
render={({ open }) => (
)}
/> tagName="h1"
placeholder="Enter hero title..."
value={title}
onChange={(value) => setAttributes({ title: value })}
style={{ fontSize: '3rem', marginBottom: '1rem' }}
/>
tagName="p"
className="subtitle"
placeholder="Enter subtitle..."
value={subtitle}
onChange={(value) => setAttributes({ subtitle: value })}
style={{ fontSize: '1.2rem', marginBottom: '2rem' }}
/>
{showCTA && (
tagName="span"
placeholder="CTA Text"
value={ctaText}
onChange={(value) => setAttributes({ ctaText: value })}
/>
value={ctaUrl}
onChange={(value) => setAttributes({ ctaUrl: value })}
/>
)}
);
}, save: (props) => {
const { attributes } = props;
const { title, subtitle, backgroundImage, overlayOpacity, showCTA, ctaText, ctaUrl } = attributes;
const blockProps = useBlockProps.save();
return (
backgroundImage: backgroundImage ? url(${backgroundImage})
: 'none',
position: 'relative',
minHeight: '500px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}>
{backgroundImage && (
className="hero-overlay"
style={{
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: rgba(0, 0, 0, ${overlayOpacity})
,
}}
/>
)}
tagName="h1"
value={title}
style={{ fontSize: '3rem', marginBottom: '1rem' }}
/> tagName="p"
className="subtitle"
value={subtitle}
style={{ fontSize: '1.2rem', marginBottom: '2rem' }}
/>
);
},
});
2. Advanced Testimonial Slider
// src/blocks/testimonial-slider/index.js
import { registerBlockType } from '@wordpress/blocks';
import { useBlockProps, InnerBlocks, InspectorControls } from '@wordpress/block-editor';
import { PanelBody, RangeControl, SelectControl } from '@wordpress/components';registerBlockType('my-blocks/testimonial-slider', {
title: 'Testimonial Slider',
description: 'A responsive testimonial slider with customizable settings.',
category: 'widgets',
icon: 'format-quote',
attributes: {
slidesToShow: {
type: 'number',
default: 3,
},
autoplay: {
type: 'boolean',
default: true,
},
autoplaySpeed: {
type: 'number',
default: 3000,
},
sliderStyle: {
type: 'string',
default: 'classic',
},
},
edit: (props) => {
const { attributes, setAttributes } = props;
const { slidesToShow, autoplay, autoplaySpeed, sliderStyle } = attributes;
const blockProps = useBlockProps();
const ALLOWED_BLOCKS = ['my-blocks/testimonial-item'];
const TEMPLATE = [
['my-blocks/testimonial-item'],
['my-blocks/testimonial-item'],
['my-blocks/testimonial-item'],
];
return (
label="Slides to Show"
value={slidesToShow}
onChange={(value) => setAttributes({ slidesToShow: value })}
min={1}
max={5}
/>
label="Slider Style"
value={sliderStyle}
options={[
{ label: 'Classic', value: 'classic' },
{ label: 'Modern', value: 'modern' },
{ label: 'Minimal', value: 'minimal' },
]}
onChange={(value) => setAttributes({ sliderStyle: value })}
/>
label="Autoplay"
checked={autoplay}
onChange={(value) => setAttributes({ autoplay: value })}
/>
{autoplay && (
label="Autoplay Speed (ms)"
value={autoplaySpeed}
onChange={(value) => setAttributes({ autoplaySpeed: value })}
min={1000}
max={10000}
step={500}
/>
)}
testimonial-slider ${sliderStyle}}>
allowedBlocks={ALLOWED_BLOCKS}
template={TEMPLATE}
templateLock={false}
/>
);
}, save: (props) => {
const { attributes } = props;
const { slidesToShow, autoplay, autoplaySpeed, sliderStyle } = attributes;
const blockProps = useBlockProps.save();
return (
className={testimonial-slider ${sliderStyle}
}
data-slides-to-show={slidesToShow}
data-autoplay={autoplay}
data-autoplay-speed={autoplaySpeed}
>
);
},
});
PHP Backend Integration
Block Registration
// includes/blocks.phpclass CustomBlocks {
public function __construct() {
add_action('init', [$this, 'register_blocks']);
add_action('enqueue_block_editor_assets', [$this, 'enqueue_editor_assets']);
add_action('wp_enqueue_scripts', [$this, 'enqueue_frontend_assets']);
}
public function register_blocks() {
// Register block types
register_block_type(__DIR__ . '/build/blocks/hero-section');
register_block_type(__DIR__ . '/build/blocks/testimonial-slider');
register_block_type(__DIR__ . '/build/blocks/cta-banner');
}
public function enqueue_editor_assets() {
wp_enqueue_script(
'custom-blocks-editor',
plugins_url('build/index.js', __FILE__),
['wp-blocks', 'wp-element', 'wp-editor', 'wp-components', 'wp-i18n']
);
wp_enqueue_style(
'custom-blocks-editor-style',
plugins_url('build/editor.css', __FILE__)
);
}
public function enqueue_frontend_assets() {
if (has_block('my-blocks/testimonial-slider')) {
wp_enqueue_script('swiper-js', 'https://unpkg.com/swiper/swiper-bundle.min.js');
wp_enqueue_style('swiper-css', 'https://unpkg.com/swiper/swiper-bundle.min.css');
wp_enqueue_script(
'testimonial-slider-frontend',
plugins_url('build/testimonial-slider.js', __FILE__),
['swiper-js']
);
}
wp_enqueue_style(
'custom-blocks-style',
plugins_url('build/style.css', __FILE__)
);
}
}
new CustomBlocks();
Dynamic Block with PHP
// includes/dynamic-blocks.phpfunction register_dynamic_post_grid_block() {
register_block_type('my-blocks/post-grid', [
'attributes' => [
'postsToShow' => [
'type' => 'number',
'default' => 6,
],
'category' => [
'type' => 'string',
'default' => '',
],
'layout' => [
'type' => 'string',
'default' => 'grid',
],
],
'render_callback' => 'render_post_grid_block',
]);
}
function render_post_grid_block($attributes) {
$posts_to_show = $attributes['postsToShow'] ?? 6;
$category = $attributes['category'] ?? '';
$layout = $attributes['layout'] ?? 'grid';
$args = [
'post_type' => 'post',
'posts_per_page' => $posts_to_show,
'post_status' => 'publish',
];
if (!empty($category)) {
$args['category_name'] = $category;
}
$query = new WP_Query($args);
if (!$query->have_posts()) {
return '
No posts found.
';
} ob_start();
?>
wp_reset_postdata();
return ob_get_clean();
}add_action('init', 'register_dynamic_post_grid_block');
Advanced Features
1. Block Patterns
function register_custom_block_patterns() {
register_block_pattern(
'my-blocks/hero-cta-pattern',
[
'title' => 'Hero with CTA',
'description' => 'A hero section with call-to-action button',
'content' => '',
'categories' => ['hero'],
]
);
}
add_action('init', 'register_custom_block_patterns');
2. Block Variations
// Add block variations
wp.blocks.registerBlockVariation('my-blocks/hero-section', {
name: 'hero-video',
title: 'Hero with Video Background',
icon: 'video-alt3',
attributes: {
backgroundType: 'video',
overlayOpacity: 0.7,
},
scope: ['inserter'],
});
3. Block Transforms
// Transform from core/cover to custom hero
transforms: {
from: [
{
type: 'block',
blocks: ['core/cover'],
transform: (attributes) => {
return createBlock('my-blocks/hero-section', {
backgroundImage: attributes.url,
title: attributes.title || '',
});
},
},
],
to: [
{
type: 'block',
blocks: ['core/cover'],
transform: (attributes) => {
return createBlock('core/cover', {
url: attributes.backgroundImage,
title: attributes.title,
});
},
},
],
},
Best Practices
1. Performance Optimization
- Lazy load frontend scripts only when blocks are present
- Use wp_enqueue_script
conditionally
- Optimize images and assets2. Accessibility
- Include proper ARIA labels
- Ensure keyboard navigation
- Maintain color contrast ratios3. Internationalization
- Use wp.i18n
for translatable strings
- Follow WordPress i18n best practices
- Test with different languages4. Testing
Build for production
npm run buildTest in multiple browsers
Validate HTML output
Check accessibility compliance
Deployment
Build Process
{
"scripts": {
"build": "wp-scripts build",
"start": "wp-scripts start",
"lint:css": "wp-scripts lint-style",
"lint:js": "wp-scripts lint-js",
"test:unit": "wp-scripts test-unit-js"
}
}
Plugin Distribution
// Plugin header
/**
* Plugin Name: Custom Enterprise Blocks
* Description: Custom Gutenberg blocks for enterprise WordPress sites
* Version: 1.0.0
* Author: Mehedi Hasan
* Text Domain: custom-blocks
*/// Prevent direct access
if (!defined('ABSPATH')) {
exit;
}
// Include main class
require_once plugin_dir_path(__FILE__) . 'includes/blocks.php';
Conclusion
Custom Gutenberg blocks empower content creators while maintaining design consistency. The key is balancing flexibility with brand guidelines, ensuring performance, and providing intuitive user experiences.
Ready to build custom blocks for your enterprise WordPress sites? Let's create powerful, reusable blocks that streamline your content creation process and maintain brand consistency across your entire web presence.