Add WooCommerce Product Category Class to Body

WooCommerce adds a product_cat-{slug} class to the product wrapper, but not to the <body> tag, so in WordPress you cannot style the header, breadcrumbs or sidebar of a single product page by its product category.

This snippet adds one class per assigned category to <body> on single product pages, for example product_cat_shoes. A rule like .product_cat_shoes .site-header { … } can then change the whole page, not only the product summary.

The snippet

<?php
add_filter( 'body_class', 'dpc_product_cat_body_class' );

/**
 * Add a product_cat_{slug} class to <body> on single product pages.
 */
function dpc_product_cat_body_class( $classes ) {
	if ( ! function_exists( 'is_product' ) || ! is_product() ) {
		return $classes;
	}

	$terms = get_the_terms( get_queried_object_id(), 'product_cat' );

	if ( ! is_array( $terms ) ) {
		return $classes;
	}

	foreach ( $terms as $term ) {
		$classes[] = 'product_cat_' . sanitize_html_class( $term->slug );
	}

	return $classes;
}

Add it with Scripts Organizer

Scripts Organizer ships this snippet in its One-Click Import library. To add it by hand:

  • In wp-admin go to Scripts Organizer → Code Blocks → Add New and give the block a title.
  • Script location: PHP.
  • Trigger location: Everywhere. The body_class filter runs on the front end, so Conditions would also work, but Everywhere keeps the setup simple.
  • Action hook: leave it empty. The code only registers filters and actions, so it can run while plugins load.
  • Paste the snippet into the PHP editor, enable the block and publish it.

The same code also works unchanged in a small plugin or an mu-plugin file in wp-content/mu-plugins/.

How it works

body_class is the filter WordPress runs when it builds the class list for <body>, in classic themes through body_class() in header.php and in block themes through the template canvas.

  • is_product() limits the snippet to single product pages. The function_exists() check keeps the site running if WooCommerce is deactivated, because the filter would otherwise call an undefined function.
  • get_queried_object_id() reads the product ID from the main query. It works before the loop starts and does not depend on the global $product. Older copies of this snippet called wc_get_product() and then ->get_id(), which is a fatal error whenever no product object is available.
  • get_the_terms() returns false or a WP_Error when there is nothing to add; the is_array() check handles both.
  • sanitize_html_class() strips characters that are not valid in a class name, which matters for non-Latin slugs.

Customise it

To add parent categories as well, so a product in Shoes → Running also gets product_cat_shoes, replace the foreach loop with this one and return array_unique( $classes ) at the end:

foreach ( $terms as $term ) {
	$classes[] = 'product_cat_' . sanitize_html_class( $term->slug );

	foreach ( get_ancestors( $term->term_id, 'product_cat', 'taxonomy' ) as $ancestor_id ) {
		$ancestor = get_term( $ancestor_id, 'product_cat' );

		if ( $ancestor instanceof WP_Term ) {
			$classes[] = 'product_cat_' . sanitize_html_class( $ancestor->slug );
		}
	}
}

On product category archives WordPress already adds tax-product_cat and term-{slug} to <body>, so the snippet leaves those pages alone.

Gotchas

  • The body class uses an underscore (product_cat_shoes), while the class WooCommerce puts on the product wrapper uses a hyphen (product_cat-shoes). Use the one that matches the element you target.
  • Page caching stores the full HTML. After you rename a category slug, clear the cache or the old class stays in the markup.
  • If the class does not appear, the theme or another plugin may replace the array later on body_class. Find all functions hooked to a WordPress action / filter shows what else runs there.

In the Scripts Organizer library Import it with one click from Scripts Organizer.

// newsletter

Three tested WordPress snippets a week

Blocks, security, performance, Tailwind, PHP and React. Copy, paste, ship. You’ll also get DPlugins product updates. No spam, and you can unsubscribe anytime.

// newsletter

Three tested WordPress snippets a week

Blocks, security, performance, Tailwind, PHP and React. Copy, paste, ship. You'll also get DPlugins product updates. No spam, and you can unsubscribe anytime.