Building a custom block for WordPress with WP_Query and a Custom Post Type

You can build some cool stuff for the WordPress block editor with the Genesis Custom Blocks plugin. The fields in the plugin alone are great, but when you pair them with native WordPress functions, etc, it really opens up the potential of the block editor for dynamic front-end templating.

For example… I have a site that has a custom post type for “docs” that lets me share developer/user docs with the users of my product. I build a theme with good search and navigation so users can easily travel around and familiarise themselves with what’s what.

I want to add one more thing though. A block I can drop on the page that recommends other docs related to the one currently being viewed.

Requirements for the block:

  • Lists and links to other docs pages
  • Only lists other docs pages that belong to the same category (related) as the current one
  • Doesn’t show the current doc
  • I can set a list max limit

In the end, it’s going to look like this:

What the block will look like on the front end.

Let’s build it.

Install and active Genesis Custom Blocks. Free one is fine for what we’re building today.

Create (have) a custom post type (CPT) for our docs. The easiest way to spin this up is with another plugin like this one. For our example the CPT’s slug is doc. Make sure the CPT is exposed to the REST API and that it has Categories allowed. Create a bunch of docs. Here’s what I span up. Note the Categories column.

Dummy “docs” with categories.

Alright. The content is ready. Let’s now build the block.

In the WP Admin, with Genesis Custom Blocks active, go Custom Blocks > Add New.

In the Block Builder screen we:

  • Give it a title of “Related Blocks”
  • Add a Range Fields with the config of:
    • min = 1
    • max = 10
    • default = 3
  • slug = related-blocks
  • Set an icon and give it a few keywords

Looks like this:

The Block Builder in Genesis Custom Blocks

Super simple here! The real magic is going to happen in our templating.

In our IDE of choice (I like VS Code), in our theme, we are going to add a few things.

Note: General best practice is to work in a child theme.

In the top-level folder for the (child) theme, add the following folders and files.

- blocks/
  - related-docs/
    - block.php
    - block.css

Genesis Custom Blocks automatically recognises this folder structure and matches the related-docs folder and its files with the block we just added in the block builder.

Let’s start in our block.php file. We are going to:

  • Use good ol’ WP_Query to loop through our CPT of docs
  • Filter the output of WP_Query to only display:
    • docs that belong to the same category as the doc currently being viewed (related)
    • Limit the list returned to the max number set in the block with the range field
    • Not include the current post in the list

Our base WP_Query loop looks like this:

<?php
// The Query
$the_query = new WP_Query();

// The Loop
if ($the_query->have_posts()) {
    while ($the_query->have_posts()) {
        $the_query->the_post();
        
    }
} else {
    // no posts found
}
/* Restore original Post Data */
wp_reset_postdata();
?>

We want to set up a few variables to pass in values for the WP_Query arguments. Those variables and how they are passed as arguments in an array looks like this:

<?php
//Variables
$categories = get_the_category();
$first_cat = $categories[0]->cat_ID;
$number_of_posts = block_value( 'number-of-rows' );

// The Query
$the_query = new WP_Query( 
    array( 
        'post_type' => 'doc',
        'posts_per_page' => $number_of_posts,
        'post__not_in' => array( $post->ID ),
        'cat' => $first_cat
        ) 
);

// The Loop
if ($the_query->have_posts()) {
    while ($the_query->have_posts()) {
        $the_query->the_post();
        
    }
} else {
    // no posts found
}
/* Restore original Post Data */
wp_reset_postdata();
?>

In the variables, the most important thing to note is our use of the block_value function. This is a function made available via Genesis Custom Blocks that lets us fetch the value a content creator adds to that range field with they use the block on a page. The other 2 variables just above it are what help us identify the category of the currently viewed page.

Looking at that array of arguments, you can see how we have:

  • Set the post type to doc
  • Limited the number of rows returned to the value set with the blocks range field
  • Told it to ignore the current page
  • Told it to only include other docs that have the same category as the current page

That’s all our logic sorted. Let’s now add our HTML markup:

<?php
//Variables
$categories = get_the_category();
$first_cat = $categories[0]->cat_ID;
$number_of_posts = block_value( 'number-of-rows' );

// The Query
$the_query = new WP_Query( 
    array( 
        'post_type' => 'doc',
        'posts_per_page' => $number_of_posts,
        'post__not_in' => array( $post->ID ),
        'cat' => $first_cat
        ) 
);

// The Loop
if ($the_query->have_posts()) {
    echo '<div class="gcb-related-docs">';
    echo '<h4>Other Docs you might find helpful.</h4>';
    while ($the_query->have_posts()) {
        $the_query->the_post();
        
        ?>
        <div class="gcb-related-docs__row">
        <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
        </svg>
            <span><?php the_title(); ?></span>
            <a href="<?php the_permalink(); ?>">View</a>
        </div>
        <?php
    }
    echo '</div>';
} else {
    // no posts found
}
/* Restore original Post Data */
wp_reset_postdata();
?>

Here we are:

  • Wrapping the content in a div with the CSS class of gcb-related-docs
  • Adding a little h4 title
  • Wrapping it row in a div with the CSS class of ‘gcb-related-docs__row’
  • Embedded svg for the icon
  • Displaying the title with the_title()
  • Linking off to the doc with and anchor and the_permalink

Not to crazy!

Let’s check in what we have. Opening up one of the docs, let’s drop in this new block.

We can add it

Here’s the form:

And here’s the output:

All is working as expected. The doc I dropped this block in to belongs to the same category (fields) as all that are listed.

To make it pretty, let’s write some CSS. What I have here leverages a bit of flexbox as well as a couple of variables setup with the active theme.

.gcb-related-docs {
    margin-top: 4rem;
    margin-bottom: 4rem;
    border-bottom: 1px solid #000;
}

.gcb-related-docs__row {
    display: flex;
    align-items: center;
    height: 6rem;
    padding-left: 1rem;
    padding-right: 1rem;
    border-top: 1px solid #000;
    border-right: 1px solid #000;
    border-left: 1px solid #000;
}

.gcb-related-docs__row svg {
    height: 2.6rem;
    width: 2.6rem;
    stroke: currentColor;
    color: var( --wp--preset--color--vivid-cyan-blue );
}

.gcb-related-docs__row span {
    font-size: 1.6rem;
    font-weight: 600;
    line-height: 1;
    margin-left: 1rem;
}

.gcb-related-docs__row a {
    margin-left: auto;
    font-size: 1.4rem;
    background-color:  #f4f4f4;
    padding: 0.6rem 1.6rem;
    display: none;
}

.gcb-related-docs__row a:hover {
    background-color: var( --wp--preset--color--vivid-cyan-blue );
    color: #fff;
}

.gcb-related-docs__row:hover a {
    display: block;
}

CSS is totally subjective to the active theme and the developer writing it, but this gives you a good idea of what can be done.

With this in place, our block now looks like this:

Very nice!

Things you could do to jazz this up further:

  • Make it prettier. Mine here is fairly chill.
  • Check for responsiveness.
  • Add a few other pieces of meta-data for each doc row. E.g. # of people who “found it helpful”. Comment count. Date last updated. etc

Hope you liked this one! With Genesis Custom Blocks being the successor to Block Lab, I get to do a lot of cool things with it. Let me know if you have any questions or idea for other blocks you’d like to see built.

Leave a Reply