Skip to content

Custom Blocks

The Custom_Blocks class (which extends Default_Blocks) lets you register custom Gutenberg blocks powered by ACF. Each block is defined as a config array with its fields and gets a dedicated PHP template for rendering.


  • Config-driven: Define blocks as arrays β€” no manual acf_register_block_type() calls
  • Automatic field groups: Fields are registered as local ACF field groups per block
  • Template rendering: Each block renders from a PHP template file
  • Deterministic keys: Field keys generated with md5() for consistency
  • Keyword search: Blocks are searchable in the Gutenberg inserter by keywords

functions/project/config/custom-blocks_config.php

Each block is an array with these properties:

PropertyTypeDescription
block_namestringBlock slug (no spaces, underscores, or special characters)
template_pathstringTemplate filename (without .php) in the blocks directory
singular_namestringDisplay name in the Gutenberg inserter
keywordsstringComma-separated search keywords
categorystringBlock category (default: 'layout')
iconstringDashicon name (default: 'editor-italic')
modestring'preview' or 'edit' (default: 'preview')
fieldsarrayArray of field configs

functions/project/config/custom-blocks_config.php
<?php
return [
[
'block_name' => 'customctablock',
'template_path' => 'custom-cta-block',
'singular_name' => 'Custom CTA Block',
'keywords' => 'Custom, CTA, Call to Action',
'fields' => [
[
'name' => 'pretitle',
'type' => 'text',
'label' => 'CTA Pretitle',
],
[
'name' => 'title',
'type' => 'text',
'label' => 'CTA Title',
],
[
'name' => 'content',
'type' => 'textarea',
'label' => 'CTA Content',
],
[
'name' => 'button',
'type' => 'link',
'label' => 'CTA Button',
],
],
],
[
'block_name' => 'customquote',
'template_path' => 'custom-quote',
'singular_name' => 'Custom Quote',
'keywords' => 'Custom, Quote, Blockquote',
'fields' => [
[
'name' => 'quote_text',
'type' => 'textarea',
'label' => 'Quote',
],
[
'name' => 'quote_author',
'type' => 'text',
'label' => 'Author',
],
],
],
];

Create the template file in functions/project/blocks/:

functions/project/blocks/custom-cta-block.php
<?php
// $data contains all ACF field values
// $block contains block metadata (className, align, etc.)
// $is_preview is true when shown in the Gutenberg editor
$pretitle = $data['pretitle'] ?? '';
$title = $data['title'] ?? '';
$content = $data['content'] ?? '';
$button = $data['button'] ?? null;
?>
<div class="c--cta-block">
<?php if ($pretitle): ?>
<p class="c--cta-block__pretitle"><?php echo esc_html($pretitle); ?></p>
<?php endif; ?>
<?php if ($title): ?>
<h2 class="c--cta-block__title"><?php echo esc_html($title); ?></h2>
<?php endif; ?>
<?php if ($content): ?>
<p class="c--cta-block__content"><?php echo esc_html($content); ?></p>
<?php endif; ?>
<?php if ($button): ?>
<a href="<?php echo esc_url($button['url']); ?>"
class="c--cta-block__button"
target="<?php echo esc_attr($button['target'] ?: '_self'); ?>">
<?php echo esc_html($button['title']); ?>
</a>
<?php endif; ?>
</div>

Block fields support all standard ACF field types:

<?php
['name' => 'title', 'type' => 'text', 'label' => 'Title'],
['name' => 'content', 'type' => 'textarea', 'label' => 'Content'],
['name' => 'body', 'type' => 'wysiwyg', 'label' => 'Body'],
['name' => 'image', 'type' => 'image', 'label' => 'Image', 'return_format' => 'array'],
['name' => 'button', 'type' => 'link', 'label' => 'Button'],
['name' => 'gallery', 'type' => 'gallery', 'label' => 'Images', 'return_format' => 'array'],
['name' => 'layout', 'type' => 'select', 'label' => 'Layout', 'choices' => ['left' => 'Left', 'right' => 'Right']],

The framework has two block classes:

ClassConfig KeyTemplate DirectoryPurpose
Default_Blocksdefault_blocksfunctions/framework/blocks/Framework-provided blocks (footnote, highlighted)
Custom_Blockscustom_blocksfunctions/project/blocks/Project-specific blocks

Custom_Blocks extends Default_Blocks and only changes the config key β€” all registration, field mapping, and rendering logic is shared.

Knowledge Check

Test your understanding of this section

Loading questions...