Creating Custom Blocks for Gutenberg Using ACF
Unlock advanced WordPress content creation by learning how to create custom blocks for Gutenberg using ACF. This guide from a seasoned developer shares

When I first started building WordPress sites, the content editor was a simple text area. For anything custom - a unique hero section, a complex service display, or even a dynamic call-to-action - you'd typically resort to custom fields, shortcodes, or hardcoding templates. It worked, but it wasn't exactly intuitive for clients. Then came Gutenberg, and with it, a new paradigm for content creation: blocks. But while Gutenberg provides many core blocks, the real power often lies in creating custom blocks for Gutenberg using ACF. From my years of building intricate WordPress plugins and full-stack applications, I've found ACF to be an absolute game-changer in bridging the gap between developer efficiency and client usability.
Think about projects like my OpenWA WhatsApp Gateway plugin for WooCommerce. While its primary function is sending order notifications, OTPs, and PDF invoices via WhatsApp, I realized early on that merchants needed an easy way to customize message templates without diving into code. Imagine if I could have offered a 'WhatsApp Message Template' block right in the editor for specific pages or posts, allowing visual customization. This is where ACF custom blocks shine - they let you define rich, structured content elements that clients can easily manage.
In this comprehensive guide, I'll walk you through my real-world approach to crafting custom Gutenberg blocks with Advanced Custom Fields (ACF). We'll cover everything from setup to advanced techniques, all backed by practical experience from my projects.
The Evolution of Content Editing: Why Custom Blocks Matter
For a long time, WordPress's Classic Editor was a simple WYSIWYG. If you needed dynamic content or a complex layout, you often had to resort to a page builder plugin, which could be heavy, or custom code and shortcodes. This was workable for me, the developer, but for clients, it meant a steep learning curve or constant requests for minor content changes.
Gutenberg fundamentally changed this. It's a block editor, meaning every piece of content - a paragraph, an image, a heading - is a self-contained 'block.' This modular approach is fantastic for creating structured content. However, the core blocks are just the beginning. Real-world projects, especially client sites, often demand unique content structures that don't fit into a standard paragraph or image block.
For instance, in my Frontend File Explorer plugin, I needed a way to display file listings and provide options for users to interact with them. While a shortcode worked, a dedicated 'File Explorer' block would be far more intuitive. Similarly, in a School ERP project I built using Laravel, the frontend display for student profiles or course listings needed specific data inputs and layouts. If this were a WordPress project, a custom block would be the ideal way to manage and display such structured data, ensuring consistency and ease of use.
Custom blocks empower you to:
- Create unique content types: Design bespoke elements like testimonials, call-to-action sections, team member profiles, or product feature lists.
- Ensure design consistency: Lock down the structure and styling, allowing clients to change content without breaking the layout.
- Improve client experience: Provide an intuitive, visual interface for editing complex content, reducing reliance on developers.
- Enhance performance: Unlike some heavy page builders, well-coded custom blocks can be lean and performant, especially when you control the output.
Why ACF is My Go-To for Custom Gutenberg Blocks
You can create custom Gutenberg blocks purely with JavaScript and React, which is how WordPress intends blocks to be built natively. I've done my fair share of React development for various full-stack projects, so I'm comfortable with it. However, when it comes to WordPress, especially for plugins or client sites where rapid development and client-friendly interfaces are paramount, ACF Pro's block feature is simply unmatched for its efficiency and ease of use.
Here's why I lean on ACF:
-
PHP-First Development: If you're a WordPress developer, you're likely already proficient in PHP. ACF allows you to define your block's fields and render its frontend output almost entirely in PHP, drastically reducing the learning curve compared to diving deep into React's component lifecycle for every block.
-
Intuitive Field Management: ACF's field group interface is incredibly robust. I've used it extensively in projects like OpenWA WhatsApp Gateway to manage complex settings, like the multiple options for WhatsApp notification triggers and custom variables. Creating fields for a custom block is as simple as creating any other ACF field group, but you assign it directly to your block. This visual field builder makes creating even complex data structures a breeze. For example, the detailed settings page for OpenWA-WhatsApp-Gateway [Screenshot: Plugin Settings Page] uses a similar intuitive structure that clients appreciate.
-
Client-Friendly Editing: Once you've defined your fields, clients get a beautiful, familiar interface in the block sidebar to input their content. No need to understand JSON structures or React states - just fill out the fields. This simplifies the content management experience significantly.
-
Rapid Prototyping and Development: I can spin up a new custom block with its associated fields and frontend rendering in a fraction of the time it would take to build a React-based block from scratch. This speed is invaluable for client projects with tight deadlines.
Setting Up Your Environment for ACF Custom Blocks
Before we dive into the code, let's ensure your environment is ready. You'll need:
- A working WordPress installation: Local development environment (like Laragon, Local by Flywheel) or a staging site. If you're starting fresh, a budget-friendly option like Hostinger shared hosting can get you up and running quickly for small projects. For more control or custom applications, I often reach for DigitalOcean droplets.
- ACF PRO: The custom block feature is exclusive to ACF PRO. Make sure it's installed and activated.
- A custom theme or plugin: I always recommend building blocks within a custom plugin. This keeps your blocks portable and independent of the theme, making them reusable across multiple projects or if the theme changes. This is exactly how I structure all my custom developments, from OpenWA WhatsApp Gateway to Frontend File Explorer - everything is encapsulated in a plugin for maximum flexibility and maintainability.
For this tutorial, we'll assume you're adding your block code to a custom plugin's main file or an `includes/blocks.php` file within your plugin, which you then include in the main plugin file.
The Core Steps to Creating Custom Blocks for Gutenberg Using ACF
Registering Your ACF Block
The first step is to tell WordPress and ACF about your new block. You do this using the `acf_register_block_type()` function, usually hooked into `acf/init`.
Here's a basic example of how I would register a simple 'Custom Hero' block:
add_action('acf/init', 'my_register_acf_blocks');
function my_register_acf_blocks() {
// Check function exists.
if( function_exists('acf_register_block_type') ) {
// Register a 'Custom Hero' block.
acf_register_block_type(array(
'name' => 'custom-hero',
'title' => __('Custom Hero'),
'description' => __('A custom hero section block.'),
'render_template' => plugin_dir_path( __FILE__ ) . 'blocks/hero-block/hero-block.php',
'category' => 'common',
'icon' => 'star-filled',
'keywords' => array( 'hero', 'custom', 'banner' ),
'mode' => 'edit',
'supports' => array(
'align' => false,
'mode' => false,
)
));
// You can register more blocks here
// acf_register_block_type(array(
// 'name' => 'whatsapp-button',
// 'title' => __('WhatsApp Button'),
// 'description' => __('A button to send WhatsApp messages.'),
// 'render_template' => plugin_dir_path( __FILE__ ) . 'blocks/whatsapp-button/whatsapp-button.php',
// 'category' => 'widgets',
// 'icon' => 'whatsapp',
// 'keywords' => array( 'whatsapp', 'button', 'contact' ),
// ));
}
}
Let's break down some key arguments I use:
name: A unique slug for your block (e.g., 'custom-hero').title: The user-friendly name displayed in the block editor.description: A brief explanation of what the block does.render_template: This is crucial. It points to the PHP file that will output the block's HTML on the frontend and in the editor. I typically create a dedicated folder for each block (e.g., `blocks/hero-block/`) to keep things organized.category: Where your block will appear in the Gutenberg inserter (e.g., 'common', 'formatting', 'widgets').icon: A Dashicon slug (e.g., 'star-filled') or a custom SVG to represent your block.keywords: Search terms to help users find your block.mode: 'edit' (default, fields displayed in the sidebar) or 'preview' (fields hidden, block renders live). I usually stick with 'edit' for the best client experience, as seen in how I designed the admin dashboard for OpenWA-WhatsApp-Gateway [Screenshot: Plugin Admin Dashboard Page], prioritizing clear and accessible settings.supports: An array to enable/disable core Gutenberg features like alignment, full height, custom class names, etc.
Defining Fields with ACF Field Groups
Once your block is registered, you need to define the fields that will power its content. This is done through ACF's Field Groups interface in the WordPress admin.
Go to `ACF -> Field Groups` and create a new Field Group. You'll add all the fields relevant to your block - text inputs for headings, text areas for descriptions, image fields for backgrounds, repeater fields for lists, or even flexible content fields for highly dynamic layouts.
The magic happens in the 'Location' rules. Instead of assigning the field group to a post type or template, you'll set it to `Block -> is equal to -> [Your Block Title]`. For our 'Custom Hero' block, you'd select 'Custom Hero'.
Consider how I structured the message templates in OpenWA-WhatsApp-Gateway [Screenshot: Plugin Template Page]. Each template has fields for different message types (new order, completed order, etc.) and allows dynamic variables. With ACF blocks, I could create a 'WhatsApp Message Template' block, and the fields for 'Message Body', 'Header Image', and 'Call to Action Button' would be directly editable within that block in the page editor, making it incredibly intuitive for users.
Crafting the Block Template (render_template)
This is where your block's output comes to life. The `render_template` file (e.g., `blocks/hero-block/hero-block.php`) is a standard PHP template. Inside this file, you'll have access to all the ACF fields you defined for the block.
Here's how I typically structure these template files:

<?php
/**
* Custom Hero Block Template.
*
* @param array $block The block settings and attributes.
* @param string $content The block inner HTML (empty).
* @param bool $is_preview True during AJAX preview.
* @param int $post_id The post ID this block is saved to.
*/
// Create id attribute for specific styling.
$id = 'hero-' . $block['id'];
if( !empty($block['anchor']) ) {
$id = $block['anchor'];
}
// Create class attribute allowing for custom 'className' and 'align' values.
$className = 'custom-hero-block';
if( !empty($block['className']) ) {
$className .= ' ' . $block['className'];
}
if( !empty($block['align']) ) {
$className .= ' align' . $block['align'];
}
// Load values and handle defaults.
$heading = get_field('hero_heading') ?: 'Default Hero Heading';
$subheading = get_field('hero_subheading') ?: 'A compelling subtitle goes here.';
$background_image = get_field('background_image');
$button_text = get_field('button_text') ?: 'Learn More';
$button_link = get_field('button_link') ?: '#';
$bg_style = '';
if ($background_image) {
$bg_style = 'background-image: url(' . esc_url($background_image['url']) . ');';
}
?>
<section id="<?php echo esc_attr($id); ?>" class="<?php echo esc_attr($className); ?>" style="<?php echo esc_attr($bg_style); ?>">
<div class="hero-content">
<h1><?php echo esc_html($heading); ?></h1>
<p><?php echo esc_html($subheading); ?></p>
<a href="<?php echo esc_url($button_link); ?>" class="button"><?php echo esc_html($button_text); ?></a>
</div>
</section>
<style type="text/css">
/* Basic inline styles for preview, ideally move to a stylesheet */
.custom-hero-block {
background-size: cover;
background-position: center;
color: #fff;
padding: 100px 20px;
text-align: center;
display: flex;
align-items: center;
justify-content: center;
min-height: 400px;
}
.custom-hero-block .hero-content {
background-color: rgba(0,0,0,0.5);
padding: 30px;
border-radius: 8px;
}
.custom-hero-block h1 {
font-size: 3em;
margin-bottom: 15px;
}
.custom-hero-block p {
font-size: 1.2em;
margin-bottom: 30px;
}
.custom-hero-block .button {
display: inline-block;
background-color: #0073aa;
color: #fff;
padding: 10px 20px;
text-decoration: none;
border-radius: 5px;
transition: background-color 0.3s ease;
}
.custom-hero-block .button:hover {
background-color: #005177;
}
</style>
A few key practices I always follow:
- Safe Data Retrieval: Use `get_field('field_name')` to retrieve field values. Always provide default values or use conditional checks (`if ($field_value)`) to prevent errors if a field is empty.
- Escaping Output: Crucially, escape all output for security (`esc_html()`, `esc_attr()`, `esc_url()`). This is non-negotiable in my plugin development, especially for user-facing content in OpenWA or Frontend File Explorer.
- Dynamic IDs and Classes: The `id` and `className` variables passed to the template (from `$block` array) are super useful. Use them to ensure unique IDs and to allow users to add custom CSS classes in the editor.
Enhancing with Block Editor Styles
For a seamless user experience, your block should look consistent in the Gutenberg editor and on the frontend. While the above template includes inline styles for demonstration, in production, I'd typically enqueue separate stylesheets.
You can define an `editor_style` argument in `acf_register_block_type()` to load a CSS file specifically for the editor. For frontend styles, you'd enqueue them as you would any other stylesheet in WordPress. For example, if I wanted to style the Frontend File Explorer differently in the admin versus the frontend, I'd use this approach.
Integrating with the WordPress Ecosystem
While ACF handles much of the heavy lifting, remember you're still within WordPress. For complex blocks requiring JavaScript interactions, you'd enqueue your scripts using `wp_enqueue_script()` and `wp_add_inline_script()`. Similarly, global styles for your blocks can be enqueued for the frontend.
For example, in a Point of Sale application I built, certain components required specific JavaScript for real-time calculations or UI interactions. If I were to integrate such a component as a Gutenberg block, its interactivity would come from enqueued scripts.
Advanced Techniques and Best Practices
Live Preview and Edit Mode
The `render_template` file is used for both the frontend and the live preview in the editor. ACF does a great job of making the editor experience feel live. However, sometimes you might need different output for the editor (e.g., placeholder content) versus the frontend. You can use the `$is_preview` variable (passed to your template) to conditionally render content.
<?php if( $is_preview ): ?>
<p>This is a preview of the Custom Hero Block. Fields are in the sidebar.</p>
<?php else: ?>
<!-- Full block content here -->
<?php endif; ?>
Contextual Data and InnerBlocks
For truly flexible layouts, you might want to allow users to insert other Gutenberg blocks *inside* your custom ACF block. This is where `InnerBlocks` comes in. You can define an `InnerBlocks` area within your `render_template` using the `<InnerBlocks />` component (though with ACF blocks, you'd typically manage this more through PHP by allowing `InnerBlocks` as a field type or through a custom JS setup). However, for most ACF-driven blocks, defining specific fields through ACF is often sufficient.
Performance Considerations
While ACF blocks are generally performant, any custom code can be optimized. Minimize database queries within your block's `render_template`. If your block fetches a lot of data, consider caching its output, especially for high-traffic pages. For projects that demand top-tier performance and caching, particularly client sites or e-commerce platforms like a WooCommerce store using my OpenWA plugin, I consistently recommend Kinsta. Their managed WordPress hosting with Google Cloud infrastructure and edge caching is built for speed.
Version Control and Deployment
When developing blocks within a custom plugin, always use version control (Git). ACF Pro allows you to save Field Groups as JSON files, which is excellent for version control and deploying changes across environments. I include these JSON files in my plugin repositories, ensuring that field groups are automatically synced when the plugin is activated or updated. This dramatically streamlines the deployment process for updates, which is vital for plugins like OpenWA WhatsApp Gateway. If you're managing custom plugins or applications, understanding how to handle updates is critical; you can read more about it here: How to Update WordPress Plugins Safely Manually.
For deploying complex custom applications, APIs, or custom plugin development servers, I find DigitalOcean offers the flexibility and control I need. It's a fantastic platform for developers who prefer managing their own infrastructure.
Real-World Application: My Experience
Looking back at my projects, I can clearly see how custom ACF blocks would have made a massive difference in empowering users and streamlining content management. For instance:
-
OpenWA WhatsApp Gateway: While the plugin's core functionality is robust, I used traditional WordPress settings pages and shortcodes for customization. Imagine a 'WhatsApp Contact Button' block that allows a merchant to drag it onto any page or product description, configure the phone number, pre-filled message, and button text directly in the block editor. This would be far more intuitive than remembering a shortcode or navigating to a separate settings page for every instance. The current settings for templates in OpenWA [Screenshot: Plugin Template Page] are effective, but a block would bring this customization to the forefront of content editing.
-
Frontend File Explorer: This plugin manages files and folders. Currently, users insert a shortcode `[frontend_file_explorer]` and can pass attributes. A 'File Explorer' block would be phenomenal. Users could insert the block, and in its sidebar, select the root directory, specify permissions, toggle features like upload or delete, all through ACF fields, with a live preview of the file structure. This significantly improves usability.
-
Need help with your project?
Comments(0)
No comments yet. Be the first to share your thoughts.



