---
title: "Build Custom Gutenberg Blocks Using ACF Blocks (No React Required)"
url: https://wpvibes.com/blog/custom-gutenberg-acf-blocks/
date: 2026-08-31
modified: 2026-08-31
author: "Rashi"
description: "Learn how ACF Blocks let you build custom Gutenberg blocks using the same PHP and get_field() calls you already use, with no React or JavaScript build pipeline required. This guide walks through three real blocks: Testimonial, Feature CTA, and Team Members."
categories:
  - "Tutorials"
tags:
  - "ACF"
  - "ACF Blocks"
  - "Custom Blocks"
  - "Gutenberg"
  - "WordPress"
image: https://wpvibes.com/wp-content/uploads/2026/08/custom-acf-blocks-1024x538.png
word_count: 3089
---

# Build Custom Gutenberg Blocks Using ACF Blocks (No React Required)

For years, building custom Gutenberg blocks meant learning React, setting up a build pipeline, and wrestling with JavaScript tooling most PHP developers never signed up for. ACF Blocks changes that. If you already know PHP and use Advanced Custom Fields on your projects, you can build production-ready custom Gutenberg blocks today - with the same familiar `get_field()` calls, no React, no npm, no build step.

This guide walks through building a real plugin with **three custom Gutenberg blocks** using ACF Blocks, progressing from simple to advanced.

## What You'll Build

Three production-ready blocks packaged as a single plugin (`wpvibes-acf-blocks`):

- **Testimonial Block**: a customer quote with author name, title, and photo

- **Feature CTA Block**: a feature card with image, heading, description, and button link

- **Team Members Block**: a responsive grid of team members using ACF's Repeater field, with configurable column count

Each block lives in its own folder inside the plugin. Adding a new block later means creating one new folder - no changes to any existing file.

## Prerequisites

- **WordPress 6.7 or later** (any version supporting ACF Blocks V3)

- **[ACF Pro](https://wpbuilderaddons.com/tutorial/acf-vs-pods/)**: required for building custom blocks. The free version of ACF does not support this. ACF Pro is $49/year for a single site.

- **Local development environment** (LocalWP, WordPress Studio, wp-env, or similar)

- **Any WordPress theme**: block themes (Twenty Twenty-Four) or classic themes both work. No child theme required since we're using a plugin.

- **Basic PHP knowledge**: you'll write PHP templates using standard ACF functions

## Step 1: Set Up the Plugin

The plugin contains all three blocks in one folder structure, organized so adding new blocks later is trivial.

### 1.1 - File structure

Create this folder inside `wp-content/plugins/`:

wpvibes-acf-blocks/
├── wpvibes-acf-blocks.php
└── blocks/
├── testimonial/
│ ├── block.json
│ ├── render.php
│ └── style.css
├── feature-cta/
│ ├── block.json
│ ├── render.php
│ └── style.css
└── team-members/
├── block.json
├── render.php
└── style.css

**Why this structure?**

- **One block = one folder** - every block owns its `block.json`, `render.php`, and `style.css` in the same place. Delete the folder and the block is gone.

- **Auto-registration** - the main plugin file scans `blocks/` and registers every folder it finds. Adding a new block never requires touching this file.

- **Per-block CSS** - each block's styles are in its own file, loaded automatically when the block appears on a page.

This is the same pattern used in our [PHP-only blocks guide](https://wpvibes.com/blog/php-only-blocks-wordpress/) - one block, one folder, self-contained.

### 1.2 - The main plugin file

Create `wpvibes-acf-blocks.php`:

<?php
/**
* Plugin Name: WPVibes ACF Blocks
* Description: Custom Gutenberg blocks built with Advanced Custom Fields — Testimonial, Feature CTA, and Team Members.
* Version: 1.0.0
* Author: WPVibes
* Requires at least: 6.7
* Requires PHP: 7.4
* Text Domain: wpvibes-acf-blocks
*/

if ( ! defined( 'ABSPATH' ) ) {
exit;
}

/**
* Register all ACF blocks by scanning the blocks/ folder.
* Adding a new block later = one new folder, zero changes here.
*/
add_action( 'init', 'wpvibes_acf_register_blocks' );

function wpvibes_acf_register_blocks() {

// Only run if ACF Pro is active.
if ( ! function_exists( 'acf_register_block_type' ) ) {
return;
}

$blocks_dir = plugin_dir_path( __FILE__ ) . 'blocks/';

if ( ! is_dir( $blocks_dir ) ) {
return;
}

// Register every block folder inside blocks/.
foreach ( glob( $blocks_dir . '*', GLOB_ONLYDIR ) as $block_folder ) {
register_block_type( $block_folder );
}
}

**What this does in plain words:**

- The plugin header identifies this folder as a WordPress plugin

- `wpvibes_acf_register_blocks()` runs on WordPress's `init` hook

- ACF Pro check first — if ACF Pro isn't active, the function exits silently (no fatal errors)

- `glob()` finds every folder inside `blocks/`

- `register_block_type( $block_folder )` tells WordPress: "there's a block here, read `block.json` for details"

**Once this loop exists, you never touch this file again.** Every new block you add as a folder inside `blocks/` registers automatically.

Activate the plugin from **Plugins → Installed Plugins**. Nothing visible yet - we haven't built any blocks.

## Step 2: Build the Testimonial Block

The Testimonial block is the simplest of the three. It shows a customer quote with author details and photo. This block introduces the complete ACF Blocks workflow - `block.json`, field group setup, PHP template, and CSS.

![Testimonial block rendered on the frontend](https://wpvibes.com/wp-content/uploads/2026/08/acf-blocks-testimonial-wordpress.png)Testimonial block rendered on the frontend

### 2.1 - Create block.json

Create `blocks/testimonial/block.json`:

{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 3,
"name": "wpvibes/testimonial",
"title": "Testimonial",
"description": "Display a customer testimonial with author details and photo.",
"category": "text",
"icon": "format-quote",
"keywords": ["testimonial", "quote", "review", "author"],
"supports": {
"align": ["wide", "full"],
"anchor": true,
"jsx": true
},
"acf": {
"mode": "preview",
"renderTemplate": "render.php"
},
"style": "file:./style.css"
}

**Line by line, in plain words:**

- **`apiVersion: 3`** - required for ACF Blocks V3. Always use v3 for new blocks.

- **`name: "wpvibes/testimonial"`** - the block's unique identifier. Format: `{namespace}/{block-slug}`.

- **`title`, `description`, `category`, `icon`, `keywords`** - how the block appears in the block inserter.

- **`supports.align`** - allows wide and full-width alignment.

- **`supports.anchor`** - lets users add an HTML anchor (for jump-to links).

- **`supports.jsx: true`** - required for ACF Blocks V3.

- **`acf.mode: "preview"`** - show the rendered block in the editor (not a form view).

- **`acf.renderTemplate`** - the PHP file that renders the block. Relative to `block.json`.

- **`style`** - the stylesheet, loaded automatically in both the editor and frontend.

### 2.2 - Create the ACF Field Group

The Testimonial block needs four fields. Create them through the ACF admin UI.

- Go to **wp-admin → ACF → Field Groups → Add New**

- Field group title: **Testimonial Block Fields**

- Add the following four fields:

-

| Field Label | Field Name | Field Type | Notes |
| ----------- | ---------- | ---------- | ----- |
| Testimonial Text | testimonial_text | Textarea | Required, Character Limit: 500 |
| Author Name | author_name | Text | Required |
| Author Title | author_title | Text | Optional, e.g., "CEO, Acme Corp" |
| Author Image | author_image | Image | Return Format: **Image Array**, Preview Size: Medium |

![Create ACF Field Group for Testimonial Block](https://wpvibes.com/wp-content/uploads/2026/08/create-acf-fields-for-testimonial.jpg)Create ACF Field Group for Testimonial Block

- Scroll down to **Settings → Location Rules**

- Set the rule: **Show this field group if → Block → is equal to → Testimonial**

- Click **Publish**

![Set ACF Field Group Settings](https://wpvibes.com/wp-content/uploads/2026/08/set-acf-fields-settings.jpg)Set ACF Field Group Settings

### 2.3 - Create render.php

Create `blocks/testimonial/render.php`:

<?php
/**
* Testimonial block render template.
*
* @var array $block The block settings and attributes.
* @var string $content The block inner HTML (empty for ACF Blocks).
* @var bool $is_preview True during backend preview render.
*/

// Get field values.
$testimonial_text = get_field( 'testimonial_text' );
$author_name = get_field( 'author_name' );
$author_title = get_field( 'author_title' );
$author_image = get_field( 'author_image' );

// Build CSS classes — always include the base class + any WordPress-added classes.
$class_name = 'wpvibes-testimonial';
if ( ! empty( $block['className'] ) ) {
$class_name .= ' ' . $block['className'];
}
if ( ! empty( $block['align'] ) ) {
$class_name .= ' align' . $block['align'];
}

// Support the anchor attribute.
$anchor = ! empty( $block['anchor'] ) ? 'id="' . esc_attr( $block['anchor'] ) . '" ' : '';
?>

<div <?php echo $anchor; ?>class="<?php echo esc_attr( $class_name ); ?>">

<?php if ( $testimonial_text ) : ?>
<blockquote>
<?php echo esc_html( $testimonial_text ); ?>
</blockquote>
<?php endif; ?>

<div>

<?php if ( $author_image ) : ?>
<img
src="<?php echo esc_url( $author_image['sizes']['thumbnail'] ); ?>"
alt="<?php echo esc_attr( $author_image['alt'] ); ?>"
width="60"
height="60"
/>
<?php endif; ?>

<div>
<?php if ( $author_name ) : ?>
<p><?php echo esc_html( $author_name ); ?></p>
<?php endif; ?>

<?php if ( $author_title ) : ?>
<p><?php echo esc_html( $author_title ); ?></p>
<?php endif; ?>
</div>

</div>

</div>

**Key points:**

- **`get_field()`** - ACF's standard function for retrieving field values. Works exactly the same in ACF Blocks as it does anywhere else in ACF.

- **`$block['className']`** - WordPress passes any classes the user added via the sidebar's Advanced section.

- **`$block['align']`** - the alignment the user picked (wide, full).

- **`$block['anchor']`** - the HTML ID the user set.

- **Every field wrapped in `if ( $field )`** - optional fields don't render empty tags.

- **`esc_html()` and `esc_url()`** - always escape output. Standard WordPress security.

**The `$author_image` array structure** (when Return Format is "Image Array"):

- `$author_image['url']` - full-size URL

- `$author_image['sizes']['thumbnail']` - thumbnail URL

- `$author_image['sizes']['medium']` - medium URL

- `$author_image['alt']` - alt text

### 2.4 - Create style.css

Create `blocks/testimonial/style.css`:

.wpvibes-testimonial {
background: #f9fafb;
padding: 32px;
border-radius: 12px;
max-width: 640px;
margin: 1.5em auto;
}

.wpvibes-testimonial__quote {
font-size: 18px;
line-height: 1.6;
color: #1f2937;
font-style: italic;
margin: 0 0 24px;
border: none;
}

.wpvibes-testimonial__author {
display: flex;
align-items: center;
gap: 12px;
}

.wpvibes-testimonial__image {
width: 60px;
height: 60px;
border-radius: 50%;
object-fit: cover;
}

.wpvibes-testimonial__name {
margin: 0;
font-weight: 600;
color: #111827;
}

.wpvibes-testimonial__title {
margin: 0;
font-size: 14px;
color: #6b7280;
}

### 2.5 - See it working

- Create a new page: **Pages → Add New**

- Click **+** in the block inserter and search for **Testimonial**

- Insert the block

- Fill in the fields on the right sidebar:

Testimonial Text: `"WPVibes plugins saved us weeks of development time. The support is fantastic."`

- Author Name: `Sarah Chen`

- Author Title: `Lead Developer, TechCorp`

- Author Image: upload a headshot

- Watch the preview update in real time

- Preview the frontend

![Testimonial field group with all 4 fields and location rule visible](https://wpvibes.com/wp-content/uploads/2026/08/ACF-blocks-testimonial-settings-wordpress-1.png?_t=1788161309)Testimonial field group with all 4 fields and location rule visible

Your first ACF block is done - no React, no build step, just PHP and CSS you already know.

## Step 3: Build the Feature CTA Block

The Feature CTA block introduces two new patterns - the **Link field** (URL + label + target in one field) and using images at different sizes. Same block structure as Testimonial, just different fields.

![Feature CTA block rendered on the frontend](https://wpvibes.com/wp-content/uploads/2026/08/feature-cta-acf-block.png)Feature CTA block rendered on the frontend

### 3.1 - Create block.json

Create `blocks/feature-cta/block.json`:

{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 3,
"name": "wpvibes/feature-cta",
"title": "Feature CTA",
"description": "A feature card with image, heading, description, and call-to-action button.",
"category": "design",
"icon": "megaphone",
"keywords": ["feature", "cta", "call to action", "card"],
"supports": {
"align": ["wide", "full"],
"anchor": true,
"jsx": true
},
"acf": {
"mode": "preview",
"renderTemplate": "render.php"
},
"style": "file:./style.css"
}

### 3.2 - Create the ACF Field Group

Create a new field group called **Feature CTA Block Fields** with these four fields:

| Field Label | Field Name | Field Type | Notes |
| ----------- | ---------- | ---------- | ----- |
| Feature Image | feature_image | Image | Return Format: **Image Array** |
| Heading | heading | Text | Required |
| Description | description | Textarea | Rows: 3 |
| Button Link | button_link | Link | Return Format: **Link Array** |

**Set the location rule:** Show this field group** if → Block → is equal to → Feature CTA.**

### 3.3 - Create render.php

Create `blocks/feature-cta/render.php`:

<?php
/**
* Feature CTA block render template.
*
* @var array $block The block settings and attributes.
*/

$feature_image = get_field( 'feature_image' );
$heading = get_field( 'heading' );
$description = get_field( 'description' );
$button_link = get_field( 'button_link' );

$class_name = 'wpvibes-feature-cta';
if ( ! empty( $block['className'] ) ) {
$class_name .= ' ' . $block['className'];
}
if ( ! empty( $block['align'] ) ) {
$class_name .= ' align' . $block['align'];
}

The full render template, with the image and heading markup included, is here: [blocks/feature-cta/render.php](https://github.com/webtechhardik/wpvibes-acf-blocks/blob/main/blocks/feature-cta/render.php).

**What's new here compared to Testimonial:**

- **The Link field returns an array** with three keys: `url`, `title`, `target`. Handle each carefully with the appropriate escape function.

- **`rel="noopener noreferrer"`** - security best practice when opening links in a new tab. Without this, the new tab can access the original window through `window.opener`.

- **Fallback for target** - if the user didn't set a target, default to `_self`.

### 3.4 - Create style.css

Create `blocks/feature-cta/style.css`:

.wpvibes-feature-cta {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
gap: 20px;
padding: 40px 32px;
background: #ffffff;
border-radius: 12px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
max-width: 400px;
margin: 1.5em auto;
}

.wpvibes-feature-cta__image {
width: 80px;
height: 80px;
}

.wpvibes-feature-cta__image img {
max-width: 100%;
max-height: 100%;
object-fit: contain;
}

### 3.5 - See it working

Insert **Feature CTA** from the Design category. Upload a small icon-style image, add a heading like "Fast Deployment," a description, and a button label with a URL. Preview the frontend.

![Feature CTA block filled in the editor](https://wpvibes.com/wp-content/uploads/2026/08/feature-cta-acf-blocks-settings-scaled-1.webp)Feature CTA block filled in the editor

Same pattern as Testimonial, more fields, one new field type. The plugin pattern is starting to feel automatic.

## Step 4: Build the Team Members Block (with Repeater)

The Team Members block is where ACF Blocks really shines. Using ACF's **Repeater field**, users add unlimited team members through the sidebar - no coding required. This is the pattern that makes ACF Pro worth the license fee.

The block also introduces a **column selector** so users pick between 2, 3, or 4 columns.

![Team Members grid rendered on the frontend](https://wpvibes.com/wp-content/uploads/2026/08/team-member-acf-block.png)Team Members grid rendered on the frontend

### 4.1 - Create block.json

Create `blocks/team-members/block.json`:

{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 3,
"name": "wpvibes/team-members",
"title": "Team Members",
"description": "Display a grid of team members using ACF Repeater fields.",
"category": "design",
"icon": "groups",
"keywords": ["team", "members", "people", "staff"],
"supports": {
"align": ["wide", "full"],
"anchor": true,
"jsx": true
},
"acf": {
"mode": "preview",
"renderTemplate": "render.php"
},
"style": "file:./style.css"
}

### 4.2 - Create the ACF Field Group

Create a field group called **Team Members Block Fields** with these fields:

**Block-level fields (things that apply to the whole grid):**

| Field Label | Field Name | Field Type | Notes |
| ----------- | ---------- | ---------- | ----- |
| Section Heading | section_heading | Text | Optional |
| Columns | columns | Text | Enter the number of columns you want |
| Team Members | team_members | **Repeater** | Layout: Block. Button Label: "Add Team Member" |

**Sub-fields inside the `team_members` repeater:**

| Sub-field Label | Sub-field Name | Sub-field Type | Notes |
| --------------- | -------------- | -------------- | ----- |
| Photo | member_image | Image | Return Format: **Image Array**, Preview Size: Medium |
| Name | member_name | Text | Required |
| Role | member_role | Text | Optional |
| Bio | member_bio | Textarea | Rows: 3, Optional |

**Set the location rule:** Show this field group **if → Block → is equal to → Team Members.**

![Team Members field group with the repeater and sub-fields visible](https://wpvibes.com/wp-content/uploads/2026/08/acf-repeater-group-team-member.png?_t=1788161504)Team Members field group with the repeater and sub-fields visible

### 4.3 - Create render.php

Create `blocks/team-members/render.php`:

<?php
/**
* Team Members block render template.
*
* @var array $block The block settings and attributes.
*/

// Block-level fields.
$section_heading = get_field( 'section_heading' );
$columns = get_field( 'columns' );
$columns = $columns ? $columns : 3;

// Build CSS classes.
$class_name = 'wpvibes-team wpvibes-team--cols-' . intval( $columns );
if ( ! empty( $block['className'] ) ) {
$class_name .= ' ' . $block['className'];
}
if ( ! empty( $block['align'] ) ) {
$class_name .= ' align' . $block['align'];
}

$anchor = ! empty( $block['anchor'] ) ? 'id="' . esc_attr( $block['anchor'] ) . '" ' : '';
?>

The full render template, with every sub-field's markup included, is here: [blocks/team-members/render.php](https://github.com/webtechhardik/wpvibes-acf-blocks/blob/main/blocks/team-members/render.php).

**Three ACF Pro functions doing the heavy lifting:**

- **`have_rows( 'team_members' )`** - checks if the repeater has any rows. Similar in shape to WordPress's `have_posts()`.

- **`the_row()`** - loads the current row's data so `get_sub_field()` calls work.

- **`get_sub_field( 'field_name' )`** - retrieves a sub-field value from the current row.

Once you understand this three-function loop, you can build any repeater-based block - image galleries, feature lists, pricing tiers, timelines, FAQ accordions. The pattern is always the same.

### 4.4 - Create style.css

Create `blocks/team-members/style.css`:

.wpvibes-team {
margin: 2em auto;
}

.wpvibes-team__grid {
display: grid;
gap: 32px;
}

.wpvibes-team--cols-2 .wpvibes-team__grid { grid-template-columns: repeat(2, 1fr); }
.wpvibes-team--cols-3 .wpvibes-team__grid { grid-template-columns: repeat(3, 1fr); }
.wpvibes-team--cols-4 .wpvibes-team__grid { grid-template-columns: repeat(4, 1fr); }

.wpvibes-team__member {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
padding: 32px 24px;
background: #ffffff;
border-radius: 12px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
}

### 4.5 - See it working

- Insert **Team Members** from the Design category

- Set section heading: "Meet Our Team"

- Set columns to 3

- Click **Add Team Member** - add several members with photo, name, role, and bio

- Watch the grid render in the editor preview

- Try changing columns to 2 or 4 to see the layout adapt

![Team Members block in the editor with 4 members and column selector visible](https://wpvibes.com/wp-content/uploads/2026/08/team-members-acf-blocks-settings.png)Team Members block in the editor with 4 members and column selector visible

Three real, production-ready blocks in one plugin - done.

## Get the Full Plugin

The complete plugin - with all three blocks, full CSS, and README - is on **[GitHub](https://github.com/webtechhardik/wpvibes-acf-blocks)**:

To install:

cd wp-content/plugins/
git clone https://github.com/webtechhardik/wpvibes-acf-blocks.git

Then activate from **Plugins → Installed Plugins**. Adding a new block:

- Create a new folder inside `blocks/` (e.g., `blocks/pricing-table/`)

- Add `block.json`, `render.php`, and `style.css` to it

- Create the matching ACF field group with location rule `Block = Pricing Table`

That's it. No changes to `wpvibes-acf-blocks.php`, no manual registration - the loader handles everything.

## Why ACF Blocks Makes Block Development Easy

Building custom Gutenberg blocks the "official" way means learning React, setting up a JavaScript build pipeline (Webpack, Babel, Node.js), and mastering the block editor's JavaScript APIs. That's a real learning curve - several weeks of investment before shipping the first block.

ACF Blocks removes almost all of that. If you already know PHP and use ACF on your projects, the entire mental shift is small:

- **You write PHP templates** - the same syntax you've been writing for years

- **You use `get_field()`** - the same function you already use everywhere else

- **ACF handles the editor experience** - field UI, live preview, validation, all automatic

- **No build step** - save PHP, refresh the browser

The tradeoff is that ACF Pro costs $49/year for a single site. For agencies and freelancers building client sites, that's a rounding error compared to the time saved on every project.

For blocks that need advanced editor experiences (drag-and-drop, deep interactivity, custom media handling), React blocks still make sense. But for the vast majority of custom blocks a WordPress site actually needs - testimonials, feature cards, team grids, pricing tables, hero sections - ACF Blocks does the job faster with less friction.

## Frequently Asked Questions

### Do I need ACF Pro, or does the free version work?
ACF Pro is required. Custom block registration is a Pro-only feature. The free ACF plugin supports custom fields on posts and pages, but not custom Gutenberg blocks. ACF Pro is $49/year for a single site.
### Can I use ACF Blocks in a child theme instead of a plugin?
Yes. The [official ACF tutorial](https://www.advancedcustomfields.com/resources/create-your-first-acf-block/) uses this approach. The plugin approach shown in this guide is better for portability - the blocks work regardless of which theme is active.
### Why does get_field() return null in my ACF Block?
The most common cause is the field group's location rule. It must be set exactly to **Block = [Block Name]**. If the rule says "Post Type" or anything else, the fields don't attach to the block. This trips up nearly every developer at least once.
### Can ACF Blocks use InnerBlocks (nested blocks)?
Yes. Add `<InnerBlocks />` to your `render.php` template. ACF officially supports this in ACF Blocks V3. This gives you container blocks where users can nest core blocks inside.
### How do I add a custom icon instead of a Dashicon?
Two ways. Use any Dashicon slug in `block.json`'s `icon` field (like `format-quote` or `groups`). Or, with WordPress 7.1+, register your own SVG icon and reference it. See our [Custom SVG Icons guide](https://wpvibes.com/blog/wordpress-custom-icons/) for the full process.
### What's the difference between ACF Blocks and PHP-only blocks (WordPress 7.0+)?
ACF Blocks require ACF Pro but support 30+ field types (repeater, image, gallery, relationship, etc.). WordPress 7.0's [PHP-only blocks](https://wpvibes.com/blog/php-only-blocks-wordpress/) are free but limited to 4 attribute types (string, integer, boolean, enum). For simple blocks, PHP-only works. For anything with images, repeaters, or complex data, ACF Blocks wins.
### How do I loop through a repeater on the frontend?
Use the standard ACF repeater loop: `have_rows()`, `the_row()`, and `get_sub_field()`. This pattern works exactly the same in an ACF Block as it does anywhere else in ACF. The Team Members block in this guide is a full working example.
### Can I ship an ACF field group in the plugin so users don't have to create it manually?
Yes, using ACF's **JSON sync** feature. Create an `acf-json/` folder in the plugin root. When users save a field group, ACF writes it to that folder. Include those JSON files in the plugin zip. When a user activates the plugin, ACF loads the field group automatically. This is the approach ACF's [official documentation](https://www.advancedcustomfields.com/resources/synchronized-json/) recommends for shipping fields with plugins or themes.

## Wrapping Up

Custom Gutenberg blocks don't require learning React. If you know PHP and use ACF on your projects, you can start shipping real blocks today - with the same familiar workflow you already use for everything else.

The takeaways:

- **The pattern is consistent** - every ACF block is `block.json` + field group + `render.php` + `style.css` in one folder

- **The location rule is critical** - always set the field group to `Block = [Block Name]`, or `get_field()` returns null

- **One block = one folder** - self-contained, easy to add, easy to remove

- **Auto-registration scales** - the plugin scans `blocks/` and registers every folder, so adding a new block requires zero changes to existing files

- **The Repeater field is the payoff** - three functions (`have_rows`, `the_row`, `get_sub_field`) unlock unlimited grid layouts, team lists, pricing tiers, and more