Should You Turn WooCommerce Attributes Into Categories?
WooCommerce attributes vs categories: what the Enable archives checkbox really creates, and how to decide when a colour or size value earns its own page.
Most of the time the answer is no. WooCommerce attributes vs categories comes up as a question because the two look interchangeable in the admin — both are taxonomies, both group products, both can produce a URL you can put in a menu. They are not interchangeable. Attributes drive variations and filtering. Categories drive navigation, and give you an archive page worth landing on from Google. Turning an attribute value into a category is occasionally right, and the test for “occasionally” is search demand, not tidiness.
The short answer#
Attributes and categories are not interchangeable: attributes drive variations and filtering, categories drive navigation and archive pages worth landing on, so turning an attribute value into a category is only occasionally right — and the test is search demand.
- Different jobs entirely. Attributes drive variations and filtering; categories drive navigation, and only they bring hierarchy, descriptions, thumbnails and breadcrumb trails.
- Attribute archives are thin. “Enable archives?” publishes attribute terms onto the standard shop grid — no per-term image, no subcategories and no distinct layout unless you override the template — and SEO plugin control over them is inconsistent; Yoast’s feature request has been open since 2018.
- Three tests, not tidiness. Promote a combination only if it is actually searched, has enough stock to be worth landing on, and will still have products next month.
- The arithmetic kills it. Twelve colours and eight sizes on one parent is 96 combinations, and across ten parents that is 960 category pages from a 2,000-product catalogue — mostly near-empty grids, plus a maintenance cost nobody budgets for.
- Build the survivors properly. When a combination passes all three tests, create it as a real product category rather than an attribute archive, and leave everything else as a filter within layered navigation.
WooCommerce attributes vs categories: what each one actually is#
A global attribute is a taxonomy registered by WooCommerce with a pa_ prefix — pa_colour, pa_size. Categories use product_cat. Both are terms attached to products, but WooCommerce registers them with very different arguments, and the differences show up the moment you leave the edit screen.
| Behaviour | Global attribute (pa_*) | Product category (product_cat) |
|---|---|---|
| Drives variations | Yes — the only thing that can | No |
| Drives layered nav and filter widgets | Yes | No, not as a facet |
| Public archive page | Only if you tick “Enable archives?” | Always |
| Hierarchy (parent/child) | No — registered as non-hierarchical | Yes |
| Term fields in admin | Name, slug, description | Name, slug, parent, description, display type, thumbnail |
| In a product’s breadcrumb trail | No | Yes |
Two rows there do most of the work. Attribute taxonomies are registered with hierarchical set to false, so you cannot nest “Red” under “Dresses” — an attribute archive is always store-wide. And attribute terms have no display type and no thumbnail, so the things a merchandiser normally does to a landing page are not available without custom work.
What “Enable archives?” actually creates#
The checkbox lives on Products → Attributes, on the edit screen for a global attribute — not on the individual terms. Ticking it is what makes WooCommerce register that pa_* taxonomy as public, so every term under it gets a front-end archive at once. Until then the taxonomy is not public, there is no URL, and there is nothing for an SEO plugin to control.
The URL is the part people get wrong. It is not /pa_colour/red/. WooCommerce strips the pa_ prefix from public URLs, so a “Colour” attribute with the slug colour produces:
https://example.com/colour/red/
The prefix was dropped in WooCommerce 3.0.4, which broke stores that already had /pa_colour/red/ URLs indexed — those started returning 404s with nothing redirecting them (woocommerce/woocommerce issue #14773). Worth knowing if you inherit an old store with dead attribute URLs in Search Console.
You can prefix the whole set at Settings → Permalinks → Optional, in the Product attribute base field. Set it to attribute and you get /attribute/colour/red/. It is empty by default, so attribute slugs sit at the site root competing with page and category slugs — if you have a page called “Colour”, you have a collision. Set a base.
Why the documentation is so thin#
The official WooCommerce doc on managing product taxonomies gives this feature one sentence — with it enabled, “you can view a page with all products having any term of this attribute” — plus an example of adding that URL to a menu. That is the whole treatment.
The brevity is honest, because the feature genuinely is thin. WooCommerce does ship a template for attribute archives, templates/taxonomy-product-attribute.php, but its entire working body is one line: it loads archive-product.php. An attribute archive is your shop grid with a different query behind it — no per-term image, no intro block, no subcategory listing, just products and, if the theme prints it, the term description.
To make one look different, the template loader picks up a file in your theme’s woocommerce folder named after the taxonomy: taxonomy-pa_colour.php for one attribute, taxonomy-product_attribute.php for all of them. Note the underscores — the override uses the real taxonomy name, though the file inside WooCommerce is hyphenated. On a block theme, WooCommerce serves its product attribute block template instead and you edit that in the site editor.
Where SEO plugins rub against attribute archives#
The moment you tick that box you have potentially hundreds of new indexable pages that nobody reviewed, and editing SEO metadata on individual attribute terms has historically been patchy. Yoast has had a feature request open since 2018 for control over product attribute output, which also reports the SEO metabox not appearing on attribute terms at all (Yoast/wordpress-seo issue #9152). Behaviour differs between Yoast, Rank Math and SEOPress and between versions of each, so check your own install. What stays consistent: an attribute archive is an ordinary public taxonomy archive, so any taxonomy-level indexing control your plugin exposes is where the switch will be.
If you want them non-indexable and your plugin will not do it, this is the core-only version. The empty check matters: is_tax() with an empty array matches every taxonomy archive, so without it a store with no global attributes would noindex its categories too.
add_filter( 'wp_robots', function ( $robots ) {
if ( ! function_exists( 'wc_get_attribute_taxonomy_names' ) ) {
return $robots;
}
$attribute_taxonomies = wc_get_attribute_taxonomy_names();
if ( ! empty( $attribute_taxonomies ) && is_tax( $attribute_taxonomies ) ) {
unset( $robots['index'] );
$robots['noindex'] = true;
$robots['follow'] = true;
}
return $robots;
} );
That is enough on a site with no SEO plugin. Yoast also hooks wp_robots, at a priority just below PHP_INT_MAX, and merges its own values over whatever is already there — so your filter runs first and then loses. Use Yoast’s own filter instead:
add_filter( 'wpseo_robots_array', function ( $robots ) {
if ( ! function_exists( 'wc_get_attribute_taxonomy_names' ) ) {
return $robots;
}
$attribute_taxonomies = wc_get_attribute_taxonomy_names();
if ( ! empty( $attribute_taxonomies ) && is_tax( $attribute_taxonomies ) ) {
$robots['index'] = 'noindex';
}
return $robots;
} );
Rank Math’s equivalent is rank_math/frontend/robots, which takes the same shape of array. Whichever you use, noindexing an archive does not remove it from your sitemap, your menus or your internal links — those are separate jobs. And if you are already working out why legitimate pages are ignored, the reasons behind category pages not getting indexed apply to attribute archives too, usually harder.
The real question: does “red XL dress” deserve a page?#
Decide with demand, not taste. A category page earns its existence when someone types the combination into a search engine and would be happy landing on a grid of those products. Three tests, in order.
1. Is the combination actually searched?#
You already have the data. In Search Console, filter Performance by the parent category’s URL and read the queries — if “red dresses” pulls impressions against your Dresses page, that is a page waiting to be built. Your site search logs are the second source, and they beat any keyword tool: they are your customers, not a national average.
The pattern repeats across catalogues: single-attribute combinations get searched, two-attribute combinations rarely do. People search “red dress” and “plus size dress”. Almost nobody searches “red XL dress”. Size in particular is a filter, not a query — shoppers pick a colour or a style first and filter for their size afterwards.
2. Is there enough stock to make it worth landing on?#
A four-column grid needs about a dozen products before it reads as a shop rather than a leftover. Three products is a dead end for the visitor and thin content to a crawler. There is no magic number, but if you cannot fill one screen, you do not have a page — you have a search result.
3. Will it still have products next month?#
Stock moves. A combination holding fourteen products today and two in six weeks is a page that will quietly rot. Seasonal colours are the classic trap. If the combination depends on one supplier drop, it is a filter.
Pass all three and build the page. Fail any one and leave it as a filter — the layered navigation you already have handles it at zero maintenance cost, and that is the right answer more often than store owners expect.
The arithmetic that kills the “a category for everything” approach#
Run the numbers before you commit. Twelve colours and eight sizes on one parent category is 96 combinations. Across ten parent categories that is 960 category pages, from a catalogue that might hold 2,000 products. Most will hold fewer than five products. Many will hold none.
The consequences are all real and all boring:
- Crawl budget goes to nothing. Googlebot spends its visits on near-empty grids instead of the pages that convert.
- Thin content at scale. Hundreds of pages differing only by a filter value, with no unique copy, is the textbook shape of a quality problem.
- Internal linking dilutes. Every category in a menu or sidebar splits the same pool of internal links across more destinations.
- The admin becomes unusable. A 960-term category checklist on the product edit screen slows down everyone who touches a product, every day.
- Deep structures surprise people. Nesting brings its own behaviour — see why parent categories often show no products.
The maintenance cost nobody budgets for#
Built by hand, every new product needs the right combination categories ticked, every discontinued line leaves a page behind, and every new colour next season needs a fresh set of categories created and populated. On a catalogue that turns over, that is a recurring job, not a project. It is the same trap as any manual grouping: the cost is not building it, it is redoing the assignment every month.
When a combination does earn a page, build it properly#
Make it a real product category, not an attribute archive. You get hierarchy, a description, an image, a display type and breadcrumbs — everything the attribute archive lacks.
Create the category in the admin first, so it has the slug and description you want. Then populate it once with a snippet. This finds every published product in Dresses carrying the red term on pa_colour and adds red-dresses without disturbing the categories already on the product:
$target = get_term_by( 'slug', 'red-dresses', 'product_cat' );
if ( $target && ! is_wp_error( $target ) ) {
$query = new WP_Query( array(
'post_type' => 'product',
'post_status' => 'publish',
'posts_per_page' => -1,
'fields' => 'ids',
'tax_query' => array(
'relation' => 'AND',
array(
'taxonomy' => 'pa_colour',
'field' => 'slug',
'terms' => 'red',
),
array(
'taxonomy' => 'product_cat',
'field' => 'slug',
'terms' => 'dresses',
),
),
) );
foreach ( $query->posts as $product_id ) {
// true = append, so existing categories are left alone.
wp_set_object_terms( $product_id, array( $target->term_id ), 'product_cat', true );
}
}
Pass the term ID, not the slug: wp_set_object_terms() creates a term from a string it cannot match, so a typo quietly gives you a second, wrong category instead of an error. Run it once from a snippet plugin or WP-CLI, then delete it. Back the database up first — it writes immediately and there is no undo.
Be clear about what that snippet is: a snapshot. It is correct the second it runs and drifts from that moment on. Products added tomorrow are not in it. For a handful of stable pages that is fine — and a handful is the number this post is arguing for.
If you want the membership to stay correct on its own#
This is the one place a plugin genuinely helps, and only after the three tests above have said the page deserves to exist. Smart Categories for WooCommerce attaches a rule set to a real product_cat term, so the result is still an ordinary WooCommerce category — term counts, breadcrumbs, menus and other plugins all behave normally. The free version builds the rule as nested AND/OR/NOT groups across 35 match fields, global attributes included, and shows the matching products and their count before you save.

The rule for the example above is two conditions: Colour is Red AND Category is Dresses. Products are re-evaluated on create or update and on a daily sweep, so a new red dress lands in the category with nobody ticking a box, and a product that stops being red leaves it. Anything you assigned by hand stays put — the plugin only removes memberships it created itself. The mechanics are covered in more depth in adding products to a category based on rules.
The Pro version generates one subcategory per attribute value, or per combination of two attributes, with a preview showing the product count for each page before anything is created. That preview is the useful part here: it shows you that 71 of your 96 planned combinations would hold fewer than five products, so you create only the ones that clear your threshold.

Being plain about the limits: it decides membership, not whether a page deserves to exist. Point it at every combination and you get the same 960 thin pages, faster.
What none of this handles for you#
Whichever route you take — manual, snippet or plugin — these stay your job:
- Meta titles, descriptions, canonicals and noindex. Your SEO plugin’s territory. Yoast, Rank Math and SEOPress all handle category terms — set the rules there before the pages go live.
- Redirects. Retire a combination category and something has to 301 it. A redirect plugin or your server config does that. Deleting a category leaves a 404 behind, exactly as happened to those old
pa_URLs — changing category structure without losing rankings covers the sequence. - Retiring pages that fell below your threshold. There is no “hide category under N products” behaviour in WooCommerce, and none of the above adds one. If a page empties out, you decide what happens to it.
- Unique copy on the page. A generated grid with no description is still thin. If a combination earns a page, it earns two paragraphs written by a person.
The short version#
Keep attributes as attributes. Leave “Enable archives?” off unless you specifically want a store-wide page for every value, and if you do turn it on, set a product attribute base and decide deliberately whether those archives should be indexable.
Promote a combination to a real category only when it passes all three tests: people search it, you can fill a screen with it, and it will still be full next month. In practice that means a handful of single-attribute pages per store — “red dresses”, “leather boots” — and almost no two-attribute ones. Everything else is a filter, and filters cost nothing.