Building a Woo Product Category Image Block with WordPress 6.9

WordPress 6.9 is out and includes updates that will improve the WooCommerce store building experience. Let’s focus on two of them with a quick tutorial:

One. I’ve already published a good amount of content on the Block Bindings API, but to quickly summarize – it’s a way to connect core blocks (like paragraphs, images, buttons, etc) to metadata in your site. For example, if you have complex post types or taxonomies with lots of meta fields (cough.. Woo.. cough) , you can use Block Bindings to show that data in your templates without writing a bunch of custom blocks from scratch.

Two. The Terms Query Loop block is similar to the regular Query Loop, but instead of looping through posts, pages, products, etc, it loops through taxonomy terms- categories, tags, or in the case of WooCommerce, product categories, brands, etc. Woo has some basic blocks here already, but a full Query Loop for terms really opens up a lot of possibilities for ecommerce, especially for advanced designs like mega menus and landing pages.

What we’re building

Someone in the Post Status Slack recently asked if there was a block to show the product category image. While there are some improvements to product blocks coming in WooCommerce 10.4, this particular block does not exist. With the Terms Query Loop showing up, it’s only a matter of time before WooCommerce provides this. But in the meantime, we can create one ourselves using the Block Bindings API.

Introducing the Woo Product Category Image block. Built with only a few lines of code.

The block can work in two contexts:

  1. In the Product Category Archive template.
  2. In a Terms Query Loop showing all product categories.

If you’ve ever messed around with images and meta on taxonomy terms, you’ll know that there’s a lot of gaps in the process. These should be native capabilities in WordPress core, but they’re not. Luckily we can create an image block variation and use the tools core provides.

Let’s break it down.

If you want to skip ahead to the full source code, you can access it here. Otherwise, let’s look at what we need to build:

Block Bindings: Server Side

The first step is registering your block bindings at the server (PHP) level.

add_action(
    'init',
    function () {
        register_block_bindings_source(
            'woo-block-bindings-demo/product-category-image',
            array(
                'label'              => __( 'Product Category Image', 'custom-bindings' ),
                'get_value_callback' => function ( array $source_args, $block_instance ) {
			$post_id = get_queried_object_id();
			if ( isset( $block_instance->context['termId'] ) ) {
				$post_id = $block_instance->context['termId'];
			}
			if ( ! $post_id ) {
				return '';
			}
			$thumbnail_id = get_term_meta( $post_id, 'thumbnail_id', true );
	    		$image = wp_get_attachment_url( $thumbnail_id );
                	return $image;
                },
		'uses_context'      => array( 'termId' ),
            )
        );
    }
);Code language: PHP (php)

In this case, we’re telling the block editor that we have a new source of data available to our block bindings, which we’re calling woo-block-bindings-demo/product-category-image.

The important piece here is the get_value_callback function where we actually get the thumbnail image and return the URL. Along the way where checking to see what term we’re looking at so we can get the correct value. On an archive page, you can get that by asking for the queried object, but inside a Term Query Loop, we can need to get that via the block’s provided context.

Registering the binding at the server level is technically the only step needed for your block to render correctly on the frontend of your site, but it won’t look very pretty in the block editor:

Screenshot of a WordPress block editor interface showing an 'Image' block connected to 'Product Category Image' with a purple background.

If we want to be able to show something more fancy, we’ll need to also use JavaScript to register our block bindings in the block editor.

Block Bindings: Client Side

Similar to the PHP version, we have a function that registers the bindings source, but this time it’s happening when the block editor is running, meaning we’ll use the WordPress core data packages to pull the image.

import {
	registerBlockBindingsSource,
} from "@wordpress/blocks";
import { __ } from "@wordpress/i18n";
import { EXPERIMENTAL_PRODUCT_CATEGORIES_STORE_NAME } from "@woocommerce/data";

registerBlockBindingsSource({
	name: "woo-block-bindings-demo/product-category-image",
	label: __("Product Category Image", "custom-bindings"), // We can skip the label, as it was already defined in the server in the previous example.
	getValues({ select, context }) {
		if (!context.termId) {
			return {
				url: "/wp-content/uploads/woocommerce-placeholder.png",
				alt: "Placeholder Image",
			};
		}

		const { termId } = context;
		const { getProductCategory } = select(
			EXPERIMENTAL_PRODUCT_CATEGORIES_STORE_NAME,
		);

		const term = getProductCategory(termId);
		return {
			url: term?.image.src,
			alt: term?.image.alt,
		};
	},
});Code language: JavaScript (javascript)

One caveat is that while you’re editing a regular archive template, there is no single category that is selected, so we need a generic placeholder image. In this example, I’m pulling the default woocommerce-placeholder, but I should probably get a proper URL, as this might be a bit fragile.

There’s a few things that I’m not including in this example. One of them is the ability to set the image’s value via the block editor. You can technically do this, but I find that in certain situations, like this, it’s not a great experience being able to edit the value. I just want to display it.

I’m also using the @woocommerce/data package rather than the normal core WordPress data store to get the product image. This is one of those gaps I mentioned above- term images are not core WordPress, so the core REST API endpoints don’t expose them (though I could write a filter to add it, I suppose). So we’re using Woo’s separate data package which has it’s own data store for Woo data.

Register Block Variation

Last we need to make this easy to insert into our templates. We’re going to use a block variation, meaning it’ll appear as a new block, but it’ll really just be a core WordPress block with our binding pre-selected:

import {
	registerBlockVariation,
} from "@wordpress/blocks";
import { __ } from "@wordpress/i18n";

// Register a block variation for the core/image block that uses the custom binding.
registerBlockVariation("core/image", {
	name: "woo-block-bindings-demo/product-category-image-block",
	title: "Product Category Image",
	icon: "media-document",
	attributes: {
		metadata: {
			bindings: {
				url: {
					source: "woo-block-bindings-demo/product-category-image",
					args: { key: "url" },
				},
			},
		},
	},
});Code language: JavaScript (javascript)

You can see I’m passing a key because that’s an expected argument. If I had multiple types of bindings here, the key would matter, but it’s actually not doing anything here. I should probably extend this binding to support the product “brand” taxonomy as well. Then I’d probably use the key. Maybe I’ll leave that as an exercise for the reader (feel free to submit a PR!).

Last note: Registering block variations is also something you could do in PHP if you wanted. The choice is yours.

Future thoughts

There’s one downside to this approach of custom blocks for everything: the block inserter is starting to get a bit…. cluttered.

This shows you just a small sampling of blocks with the word “product” in them. There’s five just on this screen alone with “product category” as part of the name.

It’s a bit hard to pick the right block, sometimes, and there’s some amount of contextual curation that could hopefully be added here, or maybe we’ll lean on patterns in the future.

The alternative (I guess) would be one block with a lot of settings in the sidebar, so I’m not sure the best approach. At this point, I’d rather have all the extra blocks with the tradeoff of complexity, which we can solve for later.

Here’s the full source code and let me know if this was helpful!

One response to “Building a Woo Product Category Image Block with WordPress 6.9”

  1. Mark Root-Wiley Avatar

    Thanks for posting this, Brian! I had a server-side-only block binding that I was putting off editor integration with, and you helped me take the plunge! I agree with you that adding editability is often the wrong UX approach for these bindings, since it muddies where the data is coming from and the site-wide implications of editing the value.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Follow Modern WordPress Development

Receive new posts in your inbox. Never spam.

Continue reading