Building a block with Advanced Custom Fields

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, you’ll likely find it 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.

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() to register our custom block. I’ve provided a summary of the parameters below, and for more information see the ACF documentation.

<?php
/**
* Register Blocks
* @see https://www.billerickson.net/building-gutenberg-block-acf/#register-block
*
*/
function be_register_blocks() {
if( ! function_exists('acf_register_block') )
return;
acf_register_block( array(
'name' => 'team-member',
'title' => __( 'Team Member', 'clientname' ),
'render_template' => 'partials/block-team-member.php',
'category' => 'formatting',
'icon' => 'admin-users',
'mode' => 'preview',
'keywords' => array( 'profile', 'user', 'author' )
));
}
add_action('acf/init', 'be_register_blocks' );
view raw functions.php hosted with ❤ by GitHub
Name

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.

Title

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.

Category

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.

Icon

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

Mode

This lets you control how the block is presented the Gutenberg block editor. If set to “Preview” it will render 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 ).

Keywords

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:

<?php
/**
* 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 a freelance WordPress developer and a contributing developer to the Genesis framework. For the past 14 years he has worked with attorneys, publishers, corporations, and non-profits, building custom websites tailored to their needs and goals.

Ready to upgrade your website?

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

Let's Talk

Reader Interactions

Comments

  1. I am trying duplicate your block team member with ACF. I don’t see ACF input fields in the backend. Do I need to create template?

    • Yes, you need to create a render template so ACF knows what the output should look like.

      If you set the mode to ‘preview’ you’ll see the ACF fields in the right panel when the block is selected. If you set the mode to ‘edit’ you’ll see the ACF fields in the main Gutenberg editor when the block is selected.

  2. Hi Bill,
    thank you for this precious article!

    If I proceed this way without a custom single.php template everything works fine and my custom block appears in the frontend.

    I’m trying to render my custom block in a custom single.php template.
    I recall the post content with this code:

    echo ”;
    echo do_shortcode(get_post_field(‘post_content’, $postid));
    echo ”;

    Everything but the custom block shows up.
    What am I doing wrong?

    Than you very much,
    Elisa

    • The raw HTML for Gutenberg blocks is not saved to post_content. Rather, a structured HTML comment is saved and then processed using a filter on the_content.

      If you view the source of your page, you’ll likely see the HTML comment for your custom block in there. I just ran your code on a site I’m building and a custom block looks like this:

      <!-- wp:acf/features {"id":"block_5c00618c8dde1","data":{"field_5c005e7e98a7c":{"5c00618f8dde2":{"field_5c005e9398a7d":"Client Carousel","field_5c005e9b98a7e":"check","field_5c005ea798a7f":"Unlike many attorney websites that feel impersonal...
      

      If you change your code to echo apply_filters( 'the_content', get_post_field( 'post_content', get_the_ID() ) ); you should see the custom block.

      Or replace that line with the_content(); which is the function traditionally used to output the post content.

      • Tank you Bill,
        that’s perfect!!!!

        Just another question and then, I promise, I won’t bother you again đŸ™‚

        When in archive page I try to get the excerpt it returns everything but my custom blocks…
        I’m trying to make a custom post type archive in this way: https://gist.github.com/billerickson/3dfb6107be416872305c7d2e1461d3da

        Any other thing I’m missing?

        Sorry, I’m just a poor graphic designer that’s trying to act as a developer đŸ™‚
        … No future?

        • You’ll need to run the ‘the_content’ filter on the excerpt to convert the HTML block comments into actual markup.

          Like this: echo apply_filters( 'the_content', get_the_excerpt() );

          But I think the issue you see is a feature, not a bug. WordPress decided not to include dynamic blocks in the excerpt for a reason. After doing the above, you’ll probably want to strip all HTML markup out of the result, like this:

          // Get the excerpt
          $excerpt = get_the_excerpt();
          
          // Load dynamic blocks
          $excerpt = apply_filters( 'the_content', $excerpt );
          
          // Strip markup
          $excerpt = wp_strip_all_tags( $excerpt );
          
          // Limit to 55 words 
          $excerpt = wp_trim_words( $excerpt, 55 );
          
          // Display 
          echo '<p>' . $excerpt . '</p>';
          
          
  3. thank you Bill!
    Finally that’s worked with a little modification:

    I had to do something like this:

    $pageid = get_the_id();
    $content_post = get_post($pageid);
    $excerpt = $content_post->post_content;
    $excerpt = apply_filters(‘the_content’, $excerpt);
    $excerpt = str_replace(‘]]>’, ‘]]>’, $excerpt);
    $excerpt = wp_strip_all_tags( $excerpt );
    $excerpt = wp_trim_words( $excerpt, 32 );

    and then echo $excerpt worked…

    Thank you very much for your help!

  4. Hi Bill,

    Thank you very much for your article. Like you, I am a big fan of ACF but it generates a lot of queries in the database.

    I thought that with the integration in Gutenberg, ACF will use the same structure as the native one in Gutenberg. Unfortunately, this is not the case and a JSON format is generated in the post table. So I wonder if it’s not worse than before.
    In the long run, I really think I’m going to the React block to optimize the data display.

    What do you think?

    • You’re right. It’s not the best data storage mechanism. I wish we had an option to provide a core markup structure to use when saving the data, especially for future compatibility.

      If I make a call to action block and change themes, it just disappears if the partial no longer exists. If it was stored as actual markup in the post, the content would still be there.

      But on the flipside, the dynamic block structure is more resilient. Let’s say you build a block and use it on a few templates. Then the client asks for something which changes the underlying markup of the block. If the actual markup were stored in the posts, you’d get the “Unexpected Error” when the markup changes and have to delete the block and start over.

      The dynamic block structure lets you separate the content / metadata of the block from the display / markup of the block, which (IMHO) is important and missing from most Gutenberg blocks.

      There’s positives and negatives to both approaches. I’m looking forward to ACF continuing to evolve and hopefully provide us data storage options. I’m also excited to see new block builders appear that don’t have the technical debt of being a metabox plugin first. I think we’ll see a lot of innovation in this space over the next year.

      • Thanks a lot for your reply Bill. I have noticed the same bugs. But like you I’m very optimitic about this new ecosystem.

Leave A Reply