Building a block with Advanced Custom Fields

2022 Update

There’s now a better way to build ACF blocks. See my post on Building ACF Blocks with block.json.

Read Now

The new block-based editor in WordPress provides a great new content editing experience, and it’s even more powerful with custom blocks.

The core blocks are built using React and JavaScript. You can build your own Gutenberg blocks with JavaScript as well. If you plan to distribute your block publicly as a plugin, I recommend building it with JavaScript to keep it lean and decrease dependencies on other libraries.

When building a single website with many unique blocks, it’s more effective to use a block-building plugin like Advanced Custom Fields. You can build blocks much faster by leveraging a tool you likely already use. This is especially useful if you’re a PHP developer with little JavaScript experience. ACF lets you build everything using PHP.

For more real-world examples of custom ACF blocks, see my article on Building a WordPress theme with Gutenberg.

What is Advanced Custom Fields

Advanced Custom Fields is a plugin for building additional content editing fields throughout the WordPress interface. You can build custom metaboxes, site options, user profile fields, and term metadata.

For a more detailed walkthrough of ACF and how it compares to similar plugins, see my Introduction to Custom Metaboxes. I plan to write more about ACF in the near future, so keep an eye on my Advanced Custom Fields category page.

ACF is the first popular metabox plugin to add support for building blocks. Other plugins are working on it as well – see CMB2 and Carbon Fields articles on Gutenberg compatibility – but ACF is leading the way.

Creating a block

There are three key parts to building a Gutenberg block with ACF:

  1. Register the block with PHP
  2. Build the block editor with the ACF user interface
  3. Describe how the block is rendered using PHP

I’ll walk you through each step required to build a simple “Team Member” block.

I’m storing the block’s markup and styles inside the theme to keep things simple, but you could also place the code in a core functionality plugin.

Register the block

We’ll use the acf_register_block_type() to register our custom block. I’ve provided a summary of the parameters below, and for more information see the ACF documentation.

You must have ACF 5.8 or later installed for this to work.

 * Register Blocks
 * @link
function be_register_blocks() {
	if( ! function_exists( 'acf_register_block_type' ) )

	acf_register_block_type( array(
		'name'			=> 'team-member',
		'title'			=> __( 'Team Member', 'clientname' ),
		'render_template'	=> 'partials/block-team-member.php',
		'category'		=> 'formatting',
		'icon'			=> 'admin-users',
		'mode'			=> 'auto',
		'keywords'		=> array( 'profile', 'user', 'author' )

add_action('acf/init', 'be_register_blocks' );

This is your unique name for the block. ACF automatically namespaces it for you, so my team-member name becomes acf/team-member in the database.


This is the title shown in the Gutenberg block editor.

Render Template

The template file used to render the block. The same template file will be used on the frontend and as the “Preview” view in the backend editor. This can either be a relative URL from the current theme, as shown above, or a full path to any file.

Alternatively, you can use the render_callback parameter to specify a function name that output’s the block’s HTML.


This determines in which section of the “Add Block” window it appears. The options provided by WP core are: common, formatting, layout, widgets, and embed.


Specify a dashicons icon to use, or a custom SVG icon.


This lets you control how the block is presented the Gutenberg block editor. The default is “auto” which renders the block to match the frontend until you select it, then it becomes an editor.

If set to “preview” it will always look like the frontend and you can edit content in the sidebar.

If set to “Edit” it appears like a metabox in the content area. The user can switch the mode by clicking the button in the top right corner, unless you specifically disable it with 'supports' => array( 'mode' => false ).


Up to three additional terms to use when a user is searching for the block. The name is always indexed so you don’t need to repeat it as a keyword here.

Build the block editor

Once the block has been registered, you can now go to Custom Fields in the admin and create your block editor. It works just like a standard metabox built in ACF.

Under the location rules, select “Block” is equal to “Team Member”.

Build the block markup

The final step is to write the markup for the block. I created a template partial in /partials/block-team-member.php , matching the render_template parameter I specified when registering the block. My template partial looks like:

 * Team Member block
 * @package      ClientName
 * @author       Bill Erickson
 * @since        1.0.0
 * @license      GPL-2.0+

$name = get_field( 'name' );
$title = get_field( 'title' );
$photo = get_field( 'photo' );
$description = get_field( 'description' );

echo '<div class="team-member">';
	echo '<div class="team-member--header">';
		if( !empty( $photo ) )
			echo wp_get_attachment_image( $photo['ID'], 'thumbnail', null, array( 'class' => 'team-member--avatar' ) );
		if( !empty( $name ) )
			echo '<h4>' . esc_html( $name ) . '</h4>';
		if( !empty( $title ) )
			echo '<h6 class="alt">' . esc_html( $title ) . '</h6>';
	echo '</div>';
	echo '<div class="team-member--content">' . apply_filters( 'ea_the_content', $description ) . '</div>';
echo '</div>';

Use the get_field() function to access the fields set in the block settings. You can then build the markup for your block based on your specific design requirements.

Building a Table of Contents block

In my article on Developing a Gutenberg Website I mentioned the Table of Contents block I built for a few clients. It dynamically lists all h2’s within the current article and links to each one.

I started by forking the WP Anchor Header plugin. My changes include:

  • Limiting it to only h2’s
  • Only running on posts that have the table-of-contents block.
  • Before automatically adding an ID to each heading, check if one exists already. Clients can manually specify the heading’s anchor link in the Gutenberg editor so we should respect that.
  • My ea_table_of_contents() function accepts a $count parameter. We use this to list the first few headings in the article on the homepage

The block doesn’t have any editable fields so I added a Message field describing how it works:

Here is the full code for the table of contents block.

Bill Erickson

Bill Erickson is the co-founder and lead developer at CultivateWP, a WordPress agency focusing on high performance sites for web publishers.

About Me
Ready to upgrade your website?

I build custom WordPress websites that look great and are easy to manage.

Let's Talk

Reader Interactions

Comments are closed. Continue the conversation with me on Twitter: @billerickson


  1. David says

    Great article Bill! If I wanted to register multiple ACF blocks, would I just repeat ‘acf_register_block_type’ within the ‘be_register_blocks’ fucntion or is there perhaps a more efficient way of coding it?

    • Bill Erickson says

      Correct, you use the acf_register_block_type() function for each block type. You could create an array of ACF block settings arrays, then foreach( $blocks as $block ) { acf_register_block_type( $block ); }, but I think that’s less readable and no more efficient.

  2. Marius Sonnentag says

    As always Bill nails it with his smart shortness. I really like your stuff!

    Coming from Genesis and i’ve been following your tutorials for years.

    Greetz from Germany

  3. Rob McDonald says

    Hey Bill great article. I wanted to ask you if you had ever thought of including the Advanced Custom Fields Pro plugin in your Site-Functionality plugin. Also, along with this any custom ACF Blocks that you have to build for a site. I just wondered if this was practical, as well as possible. I have considered this, and it is not without its challenges, so I just wondered if this was something you had tried and not seemed worthwhile.


    • Bill Erickson says

      I personally don’t think it’s worthwhile to try bundling a popular and often-updated plugin like ACF in a custom plugin I build because I’d have to keep up with the updates.

      When we first started creating ACF blocks we were keeping them in the core functionality plugin, but given how tightly integrated our custom blocks are with the theme design, it just makes sense to keep everything in one place.

      • Robin McDonald says

        Yeah, I get you on the ACF plugin. Its a lot easier to update the plugin when it’s not bundled with another. But seems like it would be easier to reuse the ACF blocks if they are in the core functionality plugin. Or, maybe not. Thanks, Bill for your feedback.


  4. Oleksandr says

    Great “Table of Contents” tutorial, thank you!
    But is it possible to parse h2 tags that were added in custom ACF block?

    • Bill Erickson says

      If you’re using the new InnerBlocks feature in ACF 5.9 then they should be picked up. If your custom block adds the markup for the h2 though, it won’t be picked up because there isn’t an h2 in the actual post content.

  5. Julie says

    Thanks Bill! Regarding the Testimonials block, is it possible for the block to only contain settings fields like how many testimonials to show and how many testimonials to show in a grid row. And in the template render of the block for it to grab ACF custom fields entered through the “edit view” of a custom post type testimonials? I am finding that when I use get_field() and the_field(), it doesn’t see any ACF fields entered outside of the block, but it CAN pull WordPress items like the_title(). So in essence, the get_field() is supposed to grab meta-values, but doesn’t outside of the block. Am I missing something?

    • Bill Erickson says

      Personally I only use `get_field()` for accessing block data, and use `get_post_meta()` for accessing post metadata. But you might be able to use it by specifying the post ID, like `get_field( ‘my_key’, get_the_ID() )`.

  6. Mark says


    (1) Do the blocks built using ACF need the plugin to be always installed to be functional?

    (2) Are these blocks useful in writing posts or for creating pages?


    • Bill Erickson says

      Yes, the ACF plugin must be active for the blocks to be functional.

      Yes, you can use the blocks when writing posts and creating pages.