Fixing "No products were found matching your selection"
The notice is not about broken product data. It means the archive query for this exact URL returned zero rows, and the cause is usually visible in the URL, the visibility terms or a stale lookup table.
The notice means the product query for this URL returned zero results. WooCommerce prints "No products were found matching your selection." from the loop/no-products-found.php template, called on the woocommerce_no_products_found action. That action only runs on product archives (the shop page, product categories and tags, attribute archives and product search) when the main query has no posts. The products may be fine. Something in the query is excluding them, and the URL, the visibility terms and two lookup tables are where it hides.
For products that are missing from listings in general, start with products not showing. This article is about the specific case where WooCommerce tells you the query came back empty.
Step 1: read the URL
The query is built from the URL, so the fastest test is to strip it. Open the same page without its query string:
| Parameter | Added by | Narrows the query to |
|---|---|---|
filter_color=red, query_type_color=or |
Attribute filter widget or block | Products with that attribute term |
min_price, max_price |
Price filter | Price range from the lookup table |
rating_filter=4 |
Rating filter | Products tagged with that rating |
orderby=price |
Sorting dropdown | Sorting only; never empties results |
s=…&post_type=product |
Product search | Search terms, minus products hidden from search |
If the clean URL shows products and the filtered one does not, the filter is the problem, not the catalogue. Filter not working covers attribute setup and AJAX filtering in depth. The two lookup-table cases below are the ones that return zero for filters that look correct.
Step 2: visibility terms
WooCommerce does not hide products with a meta query. It uses the product_visibility taxonomy. On catalogue pages, the main query excludes products carrying exclude-from-catalog. On search, it excludes exclude-from-search. When Hide out of stock items from the catalog is on, it also excludes outofstock.
Those terms are written by the product data store when a product is saved and its featured flag, stock status, rating or catalogue visibility changed. Anything that changes stock without going through a product save leaves the terms behind:
- an inventory sync that writes
_stock_statuswith SQL orupdate_post_meta(); - a CSV or ERP import that bypasses the WooCommerce product classes;
- a database copy where
wp_term_relationshipswas not migrated with the posts.
The result is a category whose products all show "In stock" in the editor but still carry outofstock, so the archive is empty. Check one product's terms:
wp post term list 123 product_visibility --fields=slug
If the terms disagree with the product, a plain re-save does not help: WooCommerce rewrites the terms only when one of those properties is marked as changed. Toggle catalogue visibility and back, then save:
wp eval 'foreach ( wc_get_products( array( "limit" => -1, "return" => "ids" ) ) as $id ) { $p = wc_get_product( $id ); $v = $p->get_catalog_visibility(); $p->set_catalog_visibility( "hidden" === $v ? "visible" : "hidden" ); $p->set_catalog_visibility( $v ); $p->save(); }'
On large catalogues, run it in batches with "page" and "limit". Then fix the sync so it uses wc_update_product_stock() or $product->set_stock_status() followed by save(), or the problem returns on the next run. Inventory management covers stock settings that interact with this.
Step 3: the price lookup table
The price filter does not read product meta. It joins wc_product_meta_lookup and compares the requested range against its min_price and max_price columns. If that table is stale or empty, which happens after imports, direct SQL price changes or a migration that skipped it, every price-filtered URL returns nothing, while the same category without min_price and max_price works.
Rebuild it from WooCommerce › Status › Tools › Product lookup tables › Regenerate. It runs in the background through Action Scheduler, so give it time on large stores and check Tools › Scheduled Actions for failures.
Attribute filters have a separate table. When Use the product attributes lookup table for catalog filtering is enabled, attribute filters read wc_product_attributes_lookup. The Regenerate the product attributes lookup table tool rebuilds that one. Starting a regeneration switches that option off, so check it is back on once the rebuild has finished.
Step 4: code that modifies the main query
Themes and plugins narrow the shop query through pre_get_posts, the woocommerce_product_query action, or the woocommerce_product_query_tax_query and woocommerce_product_query_meta_query filters. A condition written for one archive often leaks into others. A typical example is a meta query for a custom field that only some products have, applied to every product category.
Search the codebase for those hook names, and read woocommerce_product_query and pre_get_posts for how to scope a change to one archive. A quick way to see the final SQL is the Query Monitor plugin's main query panel on the empty page. Look for a product_visibility NOT IN list longer than expected, or an unexpected meta_query.
Step 5: a cached empty page
If the catalogue was briefly empty during an import or a deploy, a page cache can keep serving the "no products" version. You see products when logged in; visitors do not. Purge the page and CDN caches and load the archive in a private window.
Common mistakes
- Running Recount terms and expecting products to return. It updates the counts shown next to categories, not which products are hidden.
- Editing the notice template first. The notice is accurate; the query is empty.
- Testing a filtered URL from a bookmark. An old
filter_value for a deleted attribute term returns nothing forever. - Ignoring the shortcode difference. A page built with the products shortcode or a product collection block shows its own empty state, not this notice, so the same query problem can look like two different bugs.
Verify
- The clean archive URL lists products in a private window.
- Each filter combination you link to returns products.
product_visibilityterms on a sample product match its stock and visibility settings.- After the next scheduled stock sync, the same category still lists products.
Frequently asked
- The notice belongs to the archive templates. The products shortcode fires its own woocommerce_shortcode_products_loop_no_results action and prints nothing by default, so an empty shortcode is silent while an empty archive shows the notice.
- Yes. Copy loop/no-products-found.php into your theme's woocommerce folder, or replace the wc_no_products_found callback on the woocommerce_no_products_found action. Fix the empty query first, though: changing the text only hides the symptom.
- Only the counts shown next to categories. It does not rebuild which products carry the exclude-from-catalog or outofstock visibility terms. Saving the products through WooCommerce with catalog visibility toggled and restored, as in step 2, does that; a plain re-save does not.