Enable InnerBlocks within an ACF block to allow nesting other Gutenberg blocks inside your custom block.
Set "jsx": true in your block.json supports to enable InnerBlocks:
{
"name": "acf/hero",
"title": "Hero Section",
"category": "theme",
"acf": {
"mode": "preview",
"renderTemplate": "blocks/hero/render.php"
},
"supports": {
"align": true,
"jsx": true
}
}
In your render template, use <InnerBlocks /> to mark where nested blocks should appear:
<?php
$heading = get_field( 'heading' );
$background = get_field( 'background_image' );
$allowed = [ 'core/paragraph', 'core/button', 'core/list' ];
?>
<section class="hero-block" style="background-image: url(<?php echo esc_url( $background['url'] ); ?>)">
<div class="hero-content">
<?php if ( $heading ) : ?>
<h1><?php echo esc_html( $heading ); ?></h1>
<?php endif; ?>
<InnerBlocks allowedBlocks="<?php echo esc_attr( wp_json_encode( $allowed ) ); ?>" />
</div>
</section>
Lock the template to prevent editors from adding or removing blocks:
<InnerBlocks
allowedBlocks="<?php echo esc_attr( wp_json_encode( [ 'core/paragraph', 'core/button' ] ) ); ?>"
template="<?php echo esc_attr( wp_json_encode( [
[ 'core/paragraph', [ 'placeholder' => 'Add a description...' ] ],
[ 'core/button', [ 'text' => 'Learn More' ] ],
] ) ); ?>"
templateLock="all"
/>
Note: <InnerBlocks /> is rendered as JSX — it only works when the block is in edit mode. On the front end, the nested block content is rendered automatically in its place.