Skip to content
Product Catalog

WooCommerce Parent Category Not Showing Products? Here’s Why

WooCommerce products not showing in parent category? WordPress already includes child products, so look at display type, catalog visibility and term counts.

If your WooCommerce products are not showing in parent category pages, the usual advice — flush permalinks, clear the cache, switch to Storefront — will almost certainly waste your afternoon. In most cases the archive is doing exactly what it has been configured to do. WordPress already includes child-category products in a parent category archive by default, so “the products all live in the subcategories” is not, on its own, an explanation. Something specific has either suppressed the product loop or excluded the products from it, and there are only about seven realistic candidates.

This post works through them in order of likelihood, with the checks a developer would actually run, and finishes with the snippet people are usually looking for — plus an honest account of what that snippet costs you.

The short answer#

WordPress already includes child-category products in a parent category archive, so an empty parent is almost always the category’s Display type set to subcategories, or products hidden from the catalog, out of stock, or not published.

  • Check display type first. A category set to “Show subcategories” outputs the tiles and sets the loop total to zero, discarding products on purpose — check the term’s own Display type, then the site-wide fallback under Appearance → Customize → WooCommerce → Product Catalog.
  • Catalog visibility second. Products set to Hidden or Search results only carry the exclude-from-catalog term and drop out of archives, and a mis-mapped column during a CSV import is the usual reason an entire category goes blank at once.
  • Out-of-stock hiding third. “Hide out of stock items from the catalog” removes those products from the archive and from term recounts, which is how a category disappears from hide-empty widgets and menus altogether.
  • Product status fourth. Draft, pending and scheduled products keep their category assignments but never appear on the front end, and private products render only for administrators and shop managers — so opening the archive in a private window separates a status problem from a query problem.
  • Counts explain lists, not archives. Stale counts cached in the wc_term_counts transient only ever explain a category missing from a sidebar or menu; run Recount terms under WooCommerce → Status → Tools, but never treat the count as the cause of an empty page.

First, separate the two symptoms#

“The parent category shows nothing” covers two unrelated failures. One is the archive template rendering no products. The other is the category itself disappearing from a menu, sidebar widget or category block. They have different causes and different fixes, and conflating them is why so many threads run for forty replies without resolving anything.

What you seeMost likely cause
Subcategory tiles render, no products below themCategory display set to subcategories
“No products were found matching your selection”Catalog visibility, out-of-stock hiding, or product status
Archive works, but the category is missing from the sidebar or menuHide-empty behaviour plus a zero or stale term count
Count badge disagrees with the products on the pageTerm counts not recounted, or the wc_term_counts transient
Products appear when logged in as admin, not when logged outDraft, pending or private products

The setting behind most “products not showing in parent category” reports#

WooCommerce has a per-category Display type, and a site-wide fallback. If either resolves to subcategories, the product loop on that archive is deliberately emptied.

The site-wide setting lives in Appearance → Customize → WooCommerce → Product Catalog → Category display, whose three choices are Show products, Show subcategories and Show subcategories & products. It is stored as the woocommerce_category_archive_display option. The per-category override lives on the term edit screen at Products → Categories → (edit a category) → Display type, offering Default, Products, Subcategories and Both, and stored as display_type term meta. Term meta wins; the option is only consulted when the term meta is empty.

When the resolved value is subcategories, woocommerce_maybe_show_product_subcategories() outputs the category tiles, sets the loop total to zero with wc_set_loop_prop( 'total', 0 ), and — if this is the main query — also zeroes post_count and max_num_pages on $wp_query, so themes that render products without consulting the loop props still show nothing. The products were fetched. They were then discarded on purpose.

Two details make this hard to spot. First, the suppression only applies when subcategories actually exist — woocommerce_get_loop_display_mode() falls back to products when the term has no children, so the same setting behaves differently on different categories. Second, a parent set to show subcategories looks visually plausible: the page is full of tiles, not obviously broken. If you want both, set the display type to Both rather than writing code.

The myth: parents do not “exclude” child products#

The most repeated explanation online is that WooCommerce only shows directly assigned products, so a parent whose stock all sits in children is legitimately empty. That is not how it works, and acting on it leads people to duplicate every product into its parent category for no reason.

product_cat is registered as a hierarchical taxonomy. On a taxonomy archive, WP_Tax_Query defaults include_children to true, and for a hierarchical taxonomy that means the generated SQL matches the queried term and every descendant term. A parent category archive includes child-category products out of the box.

Counts follow the same rule. WooCommerce registers product_cat with 'update_count_callback' => '_wc_term_recount', and for a hierarchical taxonomy that function merges in each term’s ancestors and counts descendants, so a parent’s count includes products held only by its children. This is also the source of a long-standing quirk: move a subcategory from one parent to another and the old parent can keep counting its products, reported as issue #25491.

So if a parent archive is empty, the children are not the reason. Keep going down the list.

The rest, in order of likelihood#

Catalog visibility set to hidden or search-only#

Each product has a Catalog visibility setting in the Publish box: Shop and search results, Shop only, Search results only, Hidden. The last two attach an exclude-from-catalog term from the hidden product_visibility taxonomy, and WC_Query::get_tax_query() adds a NOT IN clause for it on catalog and archive queries. The product still holds its category. It still appears in the admin product list filtered by that category. It will never appear on the archive.

Note that Shop only attaches exclude-from-search instead, which does not affect a category archive — so only the last two options explain an empty page. This is the single most common cause of a genuinely blank category after an import, because several CSV importers and migration tools set visibility per row and a bad column mapping applies it to everything.

Hide out of stock items#

WooCommerce → Settings → Products → Inventory → Out of stock visibility is the checkbox labelled “Hide out of stock items from the catalog”, stored as woocommerce_hide_out_of_stock_items. With it enabled, the outofstock visibility term joins the same NOT IN exclusion list, and a seasonal category can empty itself completely without anyone touching a product.

The setting also feeds term counting: _wc_term_recount() excludes out-of-stock products when the option is on. That is how a category vanishes from a sidebar widget entirely — the count drops to zero, and anything using hide-empty stops listing it. Pushing that stock to the end of the archive instead of hiding it is usually the better trade; showing out-of-stock products last keeps the category populated and keeps the count honest.

Product status#

Draft, pending and scheduled products hold their category assignments and show up in the admin list, but never on the front end. Private products are the nastier variant: they render for anyone with read_private_products — administrators and shop managers — and nobody else, which produces the “works for me, empty for customers” report. Always confirm in a private window before diagnosing anything else.

Stale term counts#

This one only affects listings and counts, never the archive itself — but it is the reason a parent category disappears from navigation while its page still works.

On the front end WooCommerce swaps in its own counts through wc_change_term_counts(). That function returns early in the admin and during AJAX, reads product_count_product_cat term meta, and caches the result in a transient called wc_term_counts for MONTH_IN_SECONDS. The transient is deleted at the end of _wc_term_recount(). If products are changed by a route that never triggers a recount — direct SQL, a poorly written importer, or simply toggling a visibility setting — the meta and the transient both keep the old answer. WooCommerce’s own tools page is candid about it: the Recount terms action at WooCommerce → Status → Tools is described as useful when changing settings in a way that hides products from the catalog.

The behaviour has been reported repeatedly over the years, including issue #5204, where the cached counts were served indefinitely without refreshing, and issue #13191, where a category count did not update after assigning that category to a product. Worth knowing what the admin Count column actually means, too: not “products in this category”, but “published, catalog-visible products in this category or below it”.

Another post type sharing product_cat#

Some extensions and a lot of bespoke code register an extra post type against product_cat to reuse the taxonomy. Counts then stop describing your catalogue: issue #27268 reports admin category counts inflated by posts of another type entirely. If you inherited the site, check what is attached with get_taxonomy( 'product_cat' )->object_type before trusting any number.

Bulk-edit drift#

Bulk Edit only adds categories — its checklist has no way to remove one. Quick Edit can remove them, but one product at a time. After a few reorganisations you get products sitting in categories nobody intended and missing from the ones they were supposed to move to. If that is your situation, the fix is procedural rather than technical — see bulk assigning products to categories for a repeatable approach.

The diagnostic sequence#

Run these in order. Each one eliminates a block of causes rather than a single setting.

  1. Open the archive in a private window. If products appear, you have a status or visibility problem, not a query problem.
  2. Check Display type on the term. Set it explicitly to Products and reload. If products appear, you are done — decide between Products and Both and set the Customizer default to match.
  3. Ask the database what is actually in the category, ignoring visibility entirely.
wp post list --post_type=product --product_cat=outerwear 
  --post_status=any --fields=ID,post_title,post_status

product_cat is the taxonomy’s registered query var, and because the taxonomy is hierarchical the result includes child-category products. An empty result means the assignments are wrong and no amount of template work will help. A long result with every row showing draft answers the question on its own.

  1. Compare that against the stored counts. A mismatch here points at the recount, not the archive.
wp term list product_cat --fields=term_id,name,slug,parent,count
wp transient delete wc_term_counts

Then run Recount terms from WooCommerce → Status → Tools, which rebuilds the product_count_product_cat term meta and clears the transient properly. Deleting the transient by hand only buys you one page load if the underlying meta is wrong.

  1. If it is still empty, read the SQL. This shows you the real term list and the visibility exclusions in one line, which beats guessing at which plugin filtered what.
add_filter( 'posts_clauses', function ( $clauses, $query ) {
	if ( ! is_admin() && $query->is_main_query() && is_product_category() ) {
		error_log( 'WHERE: ' . $clauses['where'] );
	}
	return $clauses;
}, 999, 2 );

In a healthy parent archive the WHERE clause contains an IN list of term_taxonomy_ids — the parent plus every descendant — and a NOT IN list holding the visibility terms. One term id in the IN list means something has set include_children to false. A suspiciously large NOT IN list means a visibility filter you did not know about. Find the plugin responsible rather than layering a fix on top of it.

If you want the opposite: parent shows only its own products#

Once you know children are included by default, the request usually inverts. Stores with a deep tree often want the parent to show only directly assigned products, so “Outerwear” is a curated edit rather than every jacket on the site. Change the parsed tax query:

add_action( 'parse_tax_query', function ( $query ) {
	if ( is_admin() || ! $query->is_main_query() ) {
		return;
	}
	if ( empty( $query->tax_query->queries ) ) {
		return;
	}
	foreach ( $query->tax_query->queries as $i => $clause ) {
		if ( isset( $clause['taxonomy'] ) && 'product_cat' === $clause['taxonomy'] ) {
			$query->tax_query->queries[ $i ]['include_children'] = false;
		}
	}
} );

The hook fires after WP_Query has built its WP_Tax_Query object but before the SQL is generated, which is why editing queries in place works. Be clear-eyed about the cost, though. _wc_term_recount() still counts descendants, so the count next to the category in menus, widgets and the admin list will no longer match the number of products on the page. Layered navigation counts drift the same way, because they are derived from the same term data rather than from your modified query. There is no supported filter that makes counting direct-only; you would be reimplementing the recount, and that is a genuine maintenance burden.

A parent that shows a handful of products where it used to show hundreds is also a real ranking change, not just a layout change. If the parent has organic traffic, treat it the way you would any other category structure change, and watch the pages afterwards — thin category archives are one of the reliable ways to end up with category pages that are not indexed.

When the drift itself is the problem#

Work through the list above and one thing becomes obvious. Display type is a setting you chose. Catalog visibility is a value on a product. Stale counts are a cache. But bulk-edit drift, products stranded in an old category, a parent that looked full last quarter and looks thin now — those are all the same failure: category membership is hand-maintained state, and hand-maintained state drifts away from the catalogue it is supposed to describe.

If the drift is occasional, a quarterly audit with the WP-CLI commands above is entirely sufficient, and cheaper than another plugin. If it is predictable — everything under £20, everything published this month — a small scheduled script that calls wp_set_object_terms() from your own rules will do the job and stays in version control. Only when the rules get complicated enough that maintaining that script becomes its own project is a plugin the better trade.

Our own Smart Categories for WooCommerce takes that last route. You attach a rule set to an existing product_cat term — nested AND/OR/NOT groups over 35 match fields, with a live preview of the matching products and their count before you save — and matching products get the real term assigned in the background via Action Scheduler. Because it is the real term, counts, breadcrumbs and menus behave exactly as described in this post. It re-evaluates on product create or update, on price or stock change, and on a daily sweep, and it only removes memberships it created, so manual assignments survive. The same reasoning applies to rule-based category assignment generally, whether you build it or install it.

Adding rules to an existing WooCommerce product category
Rules can be added to a category you already have; products you assigned by hand stay put.

It will not fix the other causes on this page, and it is worth saying so plainly. It does not change display type, does not alter WooCommerce’s counting logic, does not touch catalog visibility, and does not create redirects when you restructure a tree. Those remain WooCommerce settings, and redirects and meta tags remain your SEO plugin’s job.

The short version#

A parent category archive already includes child-category products, and its count already includes descendants. So when the page is empty, check the display type first, catalog visibility second, out-of-stock hiding third, product status fourth, and the term count last — because the count only ever explains a category missing from a list, never an empty archive. Every one of those is a piece of state someone set once and nobody revisited. That is the actual bug, and it is the one worth designing around.

Keep reading

Related articles