ACF and Gutenberg: Integrating Custom Fields with Block Editor

ACF Blocks (ACF PRO) enable custom Gutenberg block creation through PHP registration combining block editor flexibility with ACF field power. From simple content blocks with custom fields to complex layouts with InnerBlocks and block templates, ACF Blocks eliminate React JavaScript requirements. This comprehensive guide teaches ACF Block registration, field integration, render callbacks, block templates, InnerBlocks support, and advanced techniques creating custom WordPress Gutenberg blocks with familiar ACF workflows.

What Are ACF Blocks?

ACF Blocks Concept (ACF PRO 5.8+):

ACF Blocks create custom Gutenberg blocks using PHP:

  • No React required: Use PHP templates
  • ACF Fields: Familiar field interface
  • Block Controls: Native block toolbar
  • Reusable: Save and reuse block instances
  • Flexible: Full block editor integration

Block vs Field Group:

Traditional Field Group:
- Attached to post type
- Single metabox location
- Fixed field order

ACF Block:
- Insertable anywhere in content
- Multiple instances per post
- Drag-and-drop positioning
- Reusable patterns

Basic Block Registration

Register Simple Block (functions.php):

function mytheme_register_acf_blocks() {
    // Check ACF function exists
    if (function_exists('acf_register_block_type')) {
        acf_register_block_type(array(
            'name' => 'testimonial',
            'title' => __('Testimonial', 'mytheme'),
            'description' => __('A custom testimonial block', 'mytheme'),
            'render_template' => get_template_directory() . '/blocks/testimonial.php',
            'category' => 'common',
            'icon' => 'format-quote',
            'keywords' => array('testimonial', 'quote', 'review'),
        ));
    }
}
add_action('acf/init', 'mytheme_register_acf_blocks');

Block Registration Parameters

Complete Configuration:

acf_register_block_type(array(
    // Required
    'name' => 'testimonial',
    'title' => __('Testimonial'),

    // Content
    'description' => __('A testimonial block'),
    'render_template' => 'blocks/testimonial.php',
    'render_callback' => 'mytheme_render_testimonial',

    // Appearance
    'category' => 'common',
    'icon' => 'format-quote',
    'keywords' => array('testimonial'),

    // Behavior
    'mode' => 'preview',
    'align' => 'full',
    'supports' => array(
        'align' => true,
        'mode' => false,
        'multiple' => true,
        'jsx' => true,
    ),

    // Style
    'enqueue_style' => get_template_directory_uri() . '/blocks/testimonial.css',
    'enqueue_script' => get_template_directory_uri() . '/blocks/testimonial.js',
    'enqueue_assets' => 'mytheme_testimonial_assets',

    // Advanced
    'post_types' => array('post', 'page'),
    'example' => array(
        'attributes' => array(
            'mode' => 'preview',
            'data' => array(
                'author' => 'John Doe',
                'quote' => 'Amazing service!',
            ),
        ),
    ),
));

Parameter Explanations:

name: Unique slug (no spaces)
title: Display name in inserter
description: Block description
render_template: PHP file path
render_callback: PHP function name
category: Block category
icon: Dashicon or custom SVG
keywords: Search terms
mode: 'preview', 'edit', or 'auto'
align: Default alignment
supports: Block capabilities
enqueue_style: CSS file URL
enqueue_script: JS file URL
post_types: Allowed post types
example: Preview data

Creating Block Template

Testimonial Block Template (blocks/testimonial.php):

<?php
/**
 * Testimonial 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
 */

// Get ACF fields
$author = get_field('author');
$company = get_field('company');
$quote = get_field('quote');
$photo = get_field('photo');
$rating = get_field('rating');

// Block ID
$block_id = 'testimonial-' . $block['id'];

// Block classes
$class_name = 'testimonial-block';
if (!empty($block['className'])) {
    $class_name .= ' ' . $block['className'];
}
if (!empty($block['align'])) {
    $class_name .= ' align' . $block['align'];
}
?>

<div id="<?php echo esc_attr($block_id); ?>" class="<?php echo esc_attr($class_name); ?>">
    <?php if ($quote) : ?>
        <blockquote class="testimonial-quote">
            <?php echo esc_html($quote); ?>
        </blockquote>
    <?php endif; ?>

    <div class="testimonial-author">
        <?php if ($photo) : ?>
            <img src="<?php echo esc_url($photo['sizes']['thumbnail']); ?>"
                 alt="<?php echo esc_attr($author); ?>"
                 class="author-photo" />
        <?php endif; ?>

        <div class="author-info">
            <?php if ($author) : ?>
                <strong class="author-name"><?php echo esc_html($author); ?></strong>
            <?php endif; ?>

            <?php if ($company) : ?>
                <span class="author-company"><?php echo esc_html($company); ?></span>
            <?php endif; ?>

            <?php if ($rating) : ?>
                <div class="rating">
                    <?php for ($i = 0; $i < $rating; $i++) : ?>
                        <span class="star">★</span>
                    <?php endfor; ?>
                </div>
            <?php endif; ?>
        </div>
    </div>
</div>

Block Field Group

Register Fields for Block:

function mytheme_register_block_fields() {
    if (function_exists('acf_add_local_field_group')) {
        acf_add_local_field_group(array(
            'key' => 'group_testimonial_block',
            'title' => 'Testimonial Block Fields',
            'fields' => array(
                array(
                    'key' => 'field_author',
                    'label' => 'Author',
                    'name' => 'author',
                    'type' => 'text',
                    'required' => 1,
                ),
                array(
                    'key' => 'field_company',
                    'label' => 'Company',
                    'name' => 'company',
                    'type' => 'text',
                ),
                array(
                    'key' => 'field_quote',
                    'label' => 'Quote',
                    'name' => 'quote',
                    'type' => 'textarea',
                    'required' => 1,
                ),
                array(
                    'key' => 'field_photo',
                    'label' => 'Photo',
                    'name' => 'photo',
                    'type' => 'image',
                    'return_format' => 'array',
                ),
                array(
                    'key' => 'field_rating',
                    'label' => 'Rating',
                    'name' => 'rating',
                    'type' => 'range',
                    'min' => 1,
                    'max' => 5,
                    'step' => 1,
                    'default_value' => 5,
                ),
            ),
            'location' => array(
                array(
                    array(
                        'param' => 'block',
                        'operator' => '==',
                        'value' => 'acf/testimonial',
                    ),
                ),
            ),
        ));
    }
}
add_action('acf/init', 'mytheme_register_block_fields');

Block Categories

Built-in Categories:

'common' - Common
'formatting' - Formatting
'layout' - Layout
'widgets' - Widgets
'embed' - Embeds

Custom Category:

function mytheme_block_categories($categories) {
    return array_merge(
        $categories,
        array(
            array(
                'slug' => 'custom-blocks',
                'title' => __('Custom Blocks', 'mytheme'),
                'icon' => 'admin-customizer',
            ),
        )
    );
}
add_filter('block_categories_all', 'mytheme_block_categories', 10, 2);

Block Icons

Dashicon Icon:

'icon' => 'format-quote'
'icon' => 'admin-users'
'icon' => 'heart'

Custom SVG Icon:

'icon' => '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 2L2 7v10c0 5.55 3.84 10.74 9 12 5.16-1.26 9-6.45 9-12V7l-10-5z"/></svg>'

Icon with Background:

'icon' => array(
    'background' => '#7e70af',
    'foreground' => '#fff',
    'src' => 'format-quote',
)

Block Modes

Mode Options:

// Preview mode (default)
'mode' => 'preview'

// Edit mode (show fields)
'mode' => 'edit'

// Auto switch
'mode' => 'auto'

Allow Mode Switching:

'supports' => array(
    'mode' => true, // Allow users to toggle
)

Block Alignment

Set Default Alignment:

'align' => 'full'
'align' => 'wide'
'align' => 'center'

Enable Alignment Control:

'supports' => array(
    'align' => true, // Enable alignment toolbar
    'align' => array('left', 'center', 'right'), // Specific alignments
)

InnerBlocks Support

Enable Nested Blocks:

'supports' => array(
    'jsx' => true,
)

Template with InnerBlocks:

<div class="container-block">
    <div class="container-header">
        <h2><?php echo esc_html(get_field('title')); ?></h2>
    </div>

    <div class="container-content">
        <InnerBlocks />
    </div>
</div>

Block Example (Preview)

Provide Preview Data:

'example' => array(
    'attributes' => array(
        'mode' => 'preview',
        'data' => array(
            'author' => 'Jane Smith',
            'company' => 'Acme Corp',
            'quote' => 'This is an amazing product!',
            'rating' => '5',
        ),
    ),
)

Enqueue Block Assets

Separate CSS/JS Files:

'enqueue_style' => get_template_directory_uri() . '/blocks/testimonial.css',
'enqueue_script' => get_template_directory_uri() . '/blocks/testimonial.js',

Asset Callback Function:

'enqueue_assets' => function() {
    wp_enqueue_style('testimonial-block', get_template_directory_uri() . '/blocks/testimonial.css');
    wp_enqueue_script('testimonial-block', get_template_directory_uri() . '/blocks/testimonial.js', array('jquery'), '1.0', true);
}

Render Callback

Use Function Instead of Template:

'render_callback' => 'mytheme_render_testimonial'

function mytheme_render_testimonial($block, $content = '', $is_preview = false, $post_id = 0) {
    $author = get_field('author');
    $quote = get_field('quote');

    echo '<div class="testimonial-block">';
    echo '<blockquote>' . esc_html($quote) . '</blockquote>';
    echo '<cite>' . esc_html($author) . '</cite>';
    echo '</div>';
}

Block Wrapper Attributes

Add Custom Attributes:

<?php
$block_attributes = get_block_wrapper_attributes(array(
    'class' => 'additional-class',
    'data-custom' => 'value',
));
?>

<div <?php echo $block_attributes; ?>>
    <!-- Block content -->
</div>

Restrict Block to Post Types

Limit Where Block Appears:

'post_types' => array('post', 'page')
'post_types' => array('portfolio')

Multiple Block Instances

Control Block Duplication:

'supports' => array(
    'multiple' => true, // Allow multiple instances
)

'supports' => array(
    'multiple' => false, // Single instance only
)

Block Templates

Pre-populate Post with Blocks:

function mytheme_register_template() {
    $post_type_object = get_post_type_object('portfolio');
    $post_type_object->template = array(
        array('acf/testimonial', array(
            'author' => 'Default Author',
            'quote' => 'Add your testimonial here',
        )),
        array('core/paragraph', array(
            'placeholder' => 'Add project description...',
        )),
    );
}
add_action('init', 'mytheme_register_template');

Complete Block Example

Hero Block with Multiple Fields:

// Registration
acf_register_block_type(array(
    'name' => 'hero',
    'title' => __('Hero Section'),
    'render_template' => 'blocks/hero.php',
    'category' => 'layout',
    'icon' => 'cover-image',
    'supports' => array(
        'align' => array('full'),
        'mode' => false,
    ),
));

// Fields
acf_add_local_field_group(array(
    'key' => 'group_hero',
    'title' => 'Hero Block',
    'fields' => array(
        array(
            'key' => 'field_hero_title',
            'label' => 'Title',
            'name' => 'title',
            'type' => 'text',
        ),
        array(
            'key' => 'field_hero_subtitle',
            'label' => 'Subtitle',
            'name' => 'subtitle',
            'type' => 'text',
        ),
        array(
            'key' => 'field_hero_bg',
            'label' => 'Background Image',
            'name' => 'background_image',
            'type' => 'image',
            'return_format' => 'url',
        ),
        array(
            'key' => 'field_hero_button',
            'label' => 'Button',
            'name' => 'button',
            'type' => 'link',
        ),
    ),
    'location' => array(
        array(
            array(
                'param' => 'block',
                'operator' => '==',
                'value' => 'acf/hero',
            ),
        ),
    ),
));

Template (blocks/hero.php):

<?php
$title = get_field('title');
$subtitle = get_field('subtitle');
$bg_image = get_field('background_image');
$button = get_field('button');

$class_name = 'hero-block';
if (!empty($block['className'])) {
    $class_name .= ' ' . $block['className'];
}
?>

<section class="<?php echo esc_attr($class_name); ?>"
         style="background-image: url(<?php echo esc_url($bg_image); ?>);">
    <div class="hero-content">
        <?php if ($title) : ?>
            <h1 class="hero-title"><?php echo esc_html($title); ?></h1>
        <?php endif; ?>

        <?php if ($subtitle) : ?>
            <p class="hero-subtitle"><?php echo esc_html($subtitle); ?></p>
        <?php endif; ?>

        <?php if ($button) : ?>
            <a href="<?php echo esc_url($button['url']); ?>"
               class="hero-button"
               target="<?php echo esc_attr($button['target'] ?: '_self'); ?>">
                <?php echo esc_html($button['title']); ?>
            </a>
        <?php endif; ?>
    </div>
</section>

Best Practices

Organize Block Files:

theme/
├── blocks/
│   ├── testimonial/
│   │   ├── block.php
│   │   ├── fields.php
│   │   ├── style.css
│   │   └── script.js
│   └── hero/
│       ├── block.php
│       ├── fields.php
│       └── style.css

Use Block Wrapper:

// Good - uses wrapper
<div <?php echo get_block_wrapper_attributes(); ?>>
    <!-- Content -->
</div>

// Better - adds custom classes
<?php
$attributes = get_block_wrapper_attributes(array(
    'class' => 'custom-class',
));
?>
<div <?php echo $attributes; ?>>
    <!-- Content -->
</div>

Escape Output:

<?php echo esc_html($text); ?>
<?php echo esc_url($url); ?>
<?php echo esc_attr($attribute); ?>

Conclusion

ACF Blocks enable custom Gutenberg block creation through PHP registration eliminating React JavaScript requirements while maintaining block editor flexibility. Register blocks with acf_register_block_type(), create field groups targeting specific blocks, render with PHP templates accessing fields via get_field(), and implement block supports for alignment, modes, and InnerBlocks. ACF Blocks combine familiar ACF workflows with modern block editor capabilities creating maintainable custom WordPress blocks through PHP.

  1. ACF Blocks Documentation
  2. acf_register_block_type() Function
  3. Block Templates
  4. InnerBlocks
  5. Block Wrapper Attributes

Call to Action

Custom block configurations need backup protection. Backup Copilot Pro backs up your WordPress ACF block files and field configurations automatically. Safeguard your Gutenberg blocks—start your free 30-day trial today!