Custom Post Loop as a Gutenberg Block

In this article:

First, let’s take a quick look at what we’ll be building together. The image below shows the output on the front end of my customizable CPT block. In this tutorial, I’m using basic markup to output a simple and subtly styled list of testimonials, but you feel free to style it up any way you’d like.

So, in effort to spend more time and familiarize myself with Gutenberg, I wanted to convert my static front page into a Gutenberg page. One of the elements that would be handy as a block was my testimonials custom post type loop.

It turns out it was pretty easy to do. There were also some controls that I added into the block editor for a small level of configuration.

In my previous post I covered how to create a custom Gutenberg block with Advanced Custom Field’s acf_register_block().

At the time of this tutorial, Gutenberg has not yet been added into core, so we’ll need the Gutenberg plugin. We’ll also need the beta version of ACF Pro, which is currently 5.8.0 Beta 2. To quickly prototype my testimonial template, I’m using Tailwind.css. You’ll notice the use of utility classes in my markup.

Setting up the CPT

I use the free CPT UI plugin for quickly setting up custom post types.

For this CPT, I want to mainly  use custom fields. So that means no classic editor or Gutenberg. In order to disable Gutenberg on CPT you need to set the “Show in REST API” to false. Set it to true if you do want to use the Gutenberg editor.

Creating the Custom Fields

Within Advanced Custom Fields I created just two fields for the testimonial; the author’s title and the quote. I’ll use the post title for the author’s name and the feature image for the author’s profile image.

Register the Testimonial Block

Within our functions.php file we’ll register our testimonial block.

functions.php

// Register Custom Blocks
add_action('acf/init', 'my_acf_init');
function my_acf_init() {
	
	// check function exists
	if( function_exists('acf_register_block') ) {

	    // register a testimonials block
	    acf_register_block(array(
	        'name'				=> 'testimonials',
	        'title'				=> __( 'Testimonials', 'wprig' ),
	        'description'		=> __( 'A custom testimonial block.', 'wprig' ),
	        'render_callback'	=> 'my_acf_block_render_callback',
	        'category'			=> 'formatting',
	        'icon'				=> 'admin-comments',
	        'keywords'			=> array( 'testimonial' ),
	    ));

	    // register other blocks
	    acf_register_block(array(
	        ...
	    ));
	}
}

Create the Testimonial Block Fields

I want some level of configuration over my testimonial block. Depending on where I use it, I’ll want to either loop testimonials with a ‘posts_per_page’ to set a limit or I’ll want to hand select which testimonials to display.

Back in Advanced Custom Fields we’ll create three fields to create this functionality.

In this field group I’ve created a button group to allow the user to select between limiting the post loop with a number or selecting from a list of published testimonials.

Both the ‘Testimonial Count’ and ‘Select Testimonials’ fields have a conditional argument to only be displayed based on the selection of the ‘Loop Argument Type’.

The Select Testimonials Post Object field should be set to return just the post ID.

Building the Testimonial Template

Within template-parts/blocks/content-testimonials.php I’ll create my template and add some Php to dynamically set the argument type in the loop.

template-parts/blocks/content-testimonials.php

<?php
/**
 * Block Name: Testimonials
 *
 * This is the template that displays the testimonials loop block.
 */

$argType = get_field( 'loop_argument_type' );
if( $argType == "count" ) :
  $args = array( 
    'orderby' => 'title',
    'post_type' => 'testimonials',
    'posts_per_page' => get_field( 'testimonial_count' )
  );
else:
  $testimonials = get_field( 'select_testimonials' );
  $args = array( 
    'orderby' => 'title',
    'post_type' => 'testimonials',
    'post__in' => $testimonials
  );
endif;

$the_query = new WP_Query( $args );

Next, I’ll start the loop and populate our testimonial fields in the template.

template-parts/blocks/content-testimonials.php

<?php
// Continued code

if ( $the_query->have_posts() ) : while ( $the_query->have_posts() ) : $the_query->the_post(); ?>
  
  <div class="rounded border-b border-solid border-grey-lighter px-6 py-4 mb-2 max-w-md">
    <div class="flex items-center">
        <img src="<?php echo get_the_post_thumbnail_url(); ?>" class="h-12 w-12 rounded-full" />
        <div class="flex flex-col ml-4">
            <b class="font-bold text-black"><?php the_title(); ?></b>
            <span class="text-grey"><?php echo get_field( 'authors_title', get_the_ID() ); ?></span>
        </div>
    </div>
    <p class=" mt-3 mb-1 leading-normal text-base">
      <?php echo get_field( 'quote', get_the_ID() ); ?>
    </p>
  </div>
  
<?php endwhile; ?>
<?php else: __( 'Sorry, there are no testimonials to display', 'wprig' );  endif; ?>

There’s one thing that I need to point out here to save you from some frustration. When looping through posts in a Gutenberg block, you must add the post ID as the second parameter when using ‘get_field()’.

Setting the Post ID as a second parameter

<?php echo get_field( 'quote', get_the_ID() ); ?>

Using the Testimonial Block

I want to add testimonials to my home page, so within the editor I added a testimonial block. I set the Loop Argument Type to Count and then set the Testimonial Count to 3.

Setting a limit to the testimonial loop

If I wanted to hand pick which testimonials I wanted to display, I could set the Loop Argument Type to Select Posts:

This is a multi-select field, so I could add as many as I’d like.

And there we have it! Our custom post type is looped and applied to a custom template which is configurable and reusable as a block throughout any post or page.