Skip to content

WooCommerce out of stock and discontinued products and SEO: leave, hide, delete or redirect

An out-of-stock WooCommerce product keeps its page. The address still answers 200 and its structured data changes to OutOfStock, so a product that is coming back needs nothing done. One that is gone for good is redirected to a true replacement or left to answer 404, never sent to the home page.

By
WP Ministry
Published
Tested on
WordPress 7.1.3, WooCommerce 11.2.0, PHP 8.3.35

In short

  • When a product runs out, WooCommerce changes availability in its structured data to https://schema.org/OutOfStock and removes the add-to-cart button. The address still answers 200, with no noindex.
  • "Hide out of stock items from the catalog" takes the product out of the shop, category pages and product search. Its own address still answers 200 and it stays in the sitemap.
  • When stock returns, the value goes back to InStock by itself. If the page was left alone, there is nothing to undo.
  • A product that is trashed, deleted, draft or private answers 404 to a visitor. WordPress can instead redirect the address to a product with a similar slug, so check what it answers.
  • Google's documentation says a removed page with no replacement should answer 404 or 410, and a page with a clear replacement should be redirected to it with a 301.
  • On a variable product with one variation sold out, the product's markup still says InStock. Only the address that names that variation says OutOfStock.

When a WooCommerce product runs out, its page stays where it is. The address still answers 200. The page prints "Out of stock" where the add-to-cart button was, and the availability in its structured data changes from https://schema.org/InStock to https://schema.org/OutOfStock. WooCommerce adds no noindex and takes nothing out of the sitemap. When stock returns, the value changes back by itself.

So a product that is coming back needs nothing done to its page. A product that is gone for good is a different decision: redirect its address to a true replacement, or let it answer 404. This page covers both, in two halves. Each half says what WooCommerce does by itself and what Google's documentation says about the answer a crawler then gets. It makes no claim about where a page is listed in search results.

The examples use a store at https://example.com that sells a Blue Kettle at /product/blue-kettle/ and a Red Kettle at /product/red-kettle/. Put your own addresses in their place.

Part one: a product that is out of stock

What changes on the product's page

The stock status is on the product's edit screen, in the "Product data" box under the Inventory tab. "Stock status" has three choices. This is what each one puts on the product's page.

"Stock status"availability in the structured dataWhat a visitor seesThe address answers
In stockhttps://schema.org/InStockThe add-to-cart button200
Out of stockhttps://schema.org/OutOfStock"Out of stock", and no add-to-cart button200
On backorderhttps://schema.org/BackOrder"Available on backorder", and the add-to-cart button200

If "Track stock quantity for this product" is ticked, there is no "Stock status" to choose. WooCommerce sets the status from the quantity and from "Allow backorders?".

Three things do not change when a product runs out.

  • The price stays. The page still shows it, and the offer in the structured data still carries it.
  • The robots tag stays. It reads max-image-preview:large before and after. WooCommerce adds no noindex to a product that is out of stock.
  • The sitemap stays. The product is still listed in WordPress's product sitemap.

WooCommerce schema markup and rich snippets covers the rest of the structured data on a product page and how to test it.

Read the value your store serves

This asks for a product's page and prints the availability in it.

bash
curl -s https://example.com/product/blue-kettle/ | grep -o '"availability":"[^"]*"'
text
"availability":"https://schema.org/OutOfStock"

Without a terminal, open the product's page, view its source, and search for availability.

Change the stock status with WP-CLI

Editing the product and choosing "Out of stock" is the usual way. With many products, WP-CLI does the same from the command line. Put the product's ID in place of 123 and the username of an administrator in place of admin. WooCommerce's commands refuse to run without --user. How to use WP-CLI covers connecting and where to run commands.

bash
wp wc product update 123 --user=admin --in_stock=false

It answers Success: Updated product 123.

What "Hide out of stock items from the catalog" does

Go to WooCommerce, then Settings, open the Products tab and choose Inventory. The row "Out of stock visibility" holds one checkbox, "Hide out of stock items from the catalog". It is unticked on a new store. WooCommerce stores it as the option woocommerce_hide_out_of_stock_items, which is no while the box is unticked and yes while it is ticked.

bash
wp option get woocommerce_hide_out_of_stock_items

This is where a product that is out of stock appears, with the box each way.

PlaceBox untickedBox ticked
The shop pageListed, with "Read more" in place of the add-to-cart buttonNot listed
Its category pageListedNot listed
A product search (?s=kettle&post_type=product)ListedNot listed
The site's ordinary search (?s=kettle)ListedStill listed
The product's own address200, with OutOfStock200, with OutOfStock
The product sitemapListedStill listed

"Related products" on another product's page depends on the theme. With a block theme and WooCommerce's own product template, that section lists only products that are in stock or on backorder, whichever way the box is set. On a classic theme, WooCommerce picks related products with a function that leaves out products that are out of stock only while the box is ticked.

So the box changes your own listings and nothing else. It does not take the page down, does not add noindex, and does not remove the address from the sitemap. A search engine that already knows the address can still fetch the page and read OutOfStock on it. What does change is that the shop and category pages stop linking to the product for as long as the box is ticked and the product is out of stock.

What Google's documentation says availability is for

Google's documentation for merchant listings says a listing can show a product's price, its availability, and shipping and return information. In that documentation availability is a recommended property of the offer, not a required one. It accepts ten values: BackOrder, Discontinued, InStock, InStoreOnly, LimitedAvailability, OnlineOnly, OutOfStock, PreOrder, PreSale and SoldOut, each with or without the https://schema.org/ in front, and it asks for only one value per offer.

WooCommerce prints three of the ten: InStock, OutOfStock and BackOrder. It has no status that produces Discontinued, SoldOut or PreOrder.

The same documentation recommends putting product markup in the HTML the server sends, because availability and price change fast. WooCommerce does that: the value in the table above is in the page as served.

What Merchant Center does with an item that is out of stock

This matters only if your store sends its products to Google Merchant Center. Merchant Center's help page for the availability attribute says these things.

  • Availability is required for every product, and takes one of four values: in_stock, out_of_stock, preorder and backorder.
  • It maps the values in your markup to its own. InStock counts as in_stock, OutOfStock as out_of_stock, and BackOrder as backorder. Discontinued and SoldOut also count as out_of_stock.
  • The values have to agree. The product data, the product's page, its structured data and the checkout must show the same availability. A mismatch gets the product disapproved.
  • A product on backorder needs a date. Merchant Center requires an availability date for backorder, and wants that date visible on the product's page. WooCommerce prints "Available on backorder" with no date, so the date is yours to add to the page.
  • A product that is temporarily out of stock stays in the product data, marked out_of_stock. The page advises against deleting an item you will sell again, because an item added back takes a significant time to show again.
  • A product you no longer sell is removed from the product data, not left there as out_of_stock.

The product data specification adds that the price must still be visible on the page of an item that is out of stock. WooCommerce keeps it there, as above. Mismatched price in Merchant Center on a WooCommerce store covers the price side of the same check.

When it comes back in stock

Set "Stock status" back to "In stock", or run the command below. The add-to-cart button returns and the structured data says InStock again from the next request. If "Hide out of stock items from the catalog" is ticked, the product returns to the shop and its category by itself.

bash
wp wc product update 123 --user=admin --in_stock=true

If the page was left published, there is nothing else to undo: the address never changed and never stopped answering 200. If a caching plugin stores whole pages, clear it, so that visitors and crawlers are not served the copy that says "Out of stock".

Part two: a product that is gone for good

What each choice gives a visitor and a crawler

What you doThe product's address answersIn the product sitemap
Leave it published and out of stock200, with OutOfStockYes
Set "Catalog visibility" to "Hidden"200. The shop, category pages and both searches stop listing itYes
Switch it to draft404No
Make it private404 for anyone who is not logged inNo
Move it to the trash404No
Delete it permanently404No

This command prints the status an address answers with, and where it redirects to if it does. It does not follow the redirect.

bash
curl -s -o /dev/null -w "%{http_code} %{redirect_url}\n" https://example.com/product/blue-kettle/
text
404

And this one lists the addresses in WordPress's product sitemap. An SEO plugin that makes its own sitemap replaces this one, and the WordPress sitemap at wp-sitemap.xml shows how to tell which one a site serves.

bash
curl -s https://example.com/wp-sitemap-posts-product-1.xml | grep -o "<loc>[^<]*</loc>"

What the trash does to the address

Moving a product to the trash does not reserve its address. WordPress renames the slug of a trashed post, so blue-kettle becomes blue-kettle__trashed, and it keeps the original in a field named _wp_desired_post_slug so that a restored post can try to take it back. WordPress has done this since version 4.5.

  • Restored before anything took the slug, the product comes back published at its old address.
  • If a new product was given the same name in the meantime, the new product has the address. The restored one becomes /product/blue-kettle-2/.
  • The trash empties itself. WordPress's documentation gives 30 days as the default, set by EMPTY_TRASH_DAYS in wp-config.php. After that the product is deleted permanently.

WordPress may redirect the address by itself

A removed product's address does not always answer 404. When WordPress has nothing at an address, it looks for a published post whose slug begins with the same words and, if it finds one, redirects there with a 301.

With a Blue Kettle in the trash and a Blue Kettle Large still published, the command above prints this:

text
301 https://example.com/product/blue-kettle-large/

The answer carries the header X-Redirect-By: WordPress, which is how to tell this redirect from one of your own. It happens the same way for a product that was deleted, or switched to draft.

So check what the old address answers before you decide it is a 404. If WordPress sends it to a product that is not a replacement, a rule of your own for that address answers first. Both rules below do that. WordPress also has a filter, do_redirect_guess_404_permalink, that switches this guessing off for the whole site.

What Google says about each answer

A 404 or a 410. Google's documentation on status codes says it treats every 4xx answer except 429 the same way. It does not use the content, and an address that was indexed is removed from the index. Its guidance on soft 404 errors says that a page you removed, with no replacement that has similar content, should answer 404 or 410. Search Console's help for the Page indexing report adds that a 404 is not necessarily a problem when a page was removed without a replacement.

A redirect. The same guidance says that when a page has moved or has a clear replacement, the old address should answer with a 301 to it. Google's page on redirects says a permanent redirect is a signal that the target should be the address shown in results.

A redirect to something unrelated. Google's guide to moving a site warns against sending many old addresses to one irrelevant destination, such as the home page, and says:

This can confuse users and might be treated as a soft 404 error.

That sentence is written about site moves. Google's pages on status codes, on soft 404 errors and on redirects do not repeat it. It is still the reason not to point every discontinued product at the home page.

A page that stays up. A discontinued product left published answers 200 and says OutOfStock, which is one of the values Google's product documentation accepts. Google describes a soft 404 as a page that tells the visitor it does not exist while answering 200, and says it leaves such pages out of Search. Its documentation does not say whether a product page that has been out of stock for a long time is read that way.

Redirect a discontinued product to its replacement

Use this when a newer model has taken the product's place. The rule goes in .htaccess on an Apache server and answers before WordPress starts, so it works whether the old product is in the trash, deleted or still published. How to set up redirects in WordPress has the other ways: a redirect plugin's list, nginx, and code.

  1. Step 1: Ask what the old address answers now

    Run the command under "What each choice gives a visitor and a crawler" for the old product's address. Note whether it prints 200, 404, or a 301 that WordPress made.

  2. Step 2: Add the rule above WordPress's block

    Put the rule at the top of .htaccess, above the line # BEGIN WordPress. WordPress rewrites the lines between its two markers, so a rule placed there is lost.

    .htaccess
    RedirectMatch 301 "^/product/blue-kettle/?$" "/product/red-kettle/"
  3. Step 3: Ask again

    The command should now print 301 and the replacement's address.

    text
    301 https://example.com/product/red-kettle/
  4. Step 4: Follow it to the end

    Open the old address in a browser. It should land on the replacement's page, and that page should answer 200 with no further redirect. The redirect checker shows each hop without a terminal.

  5. Step 5: Move the old product to the trash

    With the redirect in place, the old product's own page can no longer be reached. Trash it, so that it leaves the shop and the sitemap.

To undo the redirect, remove the line and save.

Or say plainly that it is gone

For a product with nothing like it, a 404 is enough, and trashing the product gives you one. Use this rule when WordPress redirects the old address to a product that is not a replacement, or when you want the answer to say the page was removed on purpose. It goes in the same place, above # BEGIN WordPress.

.htaccess
RedirectMatch gone "^/product/blue-kettle/?$"
Answers 410 for /product/blue-kettle/, with or without the final slash.

The address then answers 410 with Apache's own short page, not your theme's "Page not found" page. Google's documentation treats a 410 the same as a 404.

Which to choose

The situationWhat to doWhat the address answers
Back soonLeave the page published. Set "Stock status" to "Out of stock", or to "On backorder" if you take orders now200, with OutOfStock or BackOrder
Back at a date you do not knowLeave the page published and out of stock. Tick "Hide out of stock items from the catalog" if you do not want it in the shop meanwhile200, with OutOfStock
Replaced by a newer modelRedirect the old address to the new model's page, then trash the old product301 to the replacement, which answers 200
Gone, with nothing like itTrash the product. Check that the address answers 404, and add the 410 rule if WordPress redirects it somewhere unrelated404 or 410

A variable product with one variation out of stock

On a variable product, each variation has its own stock status. With a Tea Tin sold in Small at 10.00 and Large at 14.00, and Small set to "Out of stock", this is what the pages say.

AddressThe offer in the structured dataavailability
/product/tea-tin/An AggregateOffer from 10.00 to 14.00, counting 2 offershttps://schema.org/InStock
/product/tea-tin/?attribute_size=SmallOne Offer at 10.00https://schema.org/OutOfStock
/product/tea-tin/?attribute_size=LargeOne Offer at 14.00https://schema.org/InStock

The product's own address says InStock as long as one variation can be bought, and its price range still starts at the price of the variation that is sold out. It says OutOfStock only when every variation is out of stock.

"Hide out of stock items from the catalog" reaches this too, late. With the box ticked, the sold-out variation's price left the range only after the variable product was saved again, and the markup then became one Offer at 14.00. WooCommerce's "Clear transients" tool did not bring that forward.

WooCommerce variations and SEO covers the addresses that name a variation and what each one tells a search engine.

When to get help

  • Ask your host if you cannot edit .htaccess, or if the server is nginx and you cannot change its configuration. A redirect plugin does the same job from the dashboard.
  • Hand it over if a store has many discontinued products and each needs a decision. WP Ministry's WooCommerce SEO service is the one for a store. To start from a written list of what to fix, a WordPress SEO audit is a one-time, written audit of what stands between a WordPress or WooCommerce site and a search engine.

Common questions

Should I delete out-of-stock products in WooCommerce?

Not if they are coming back. A product that is out of stock keeps its address, answers 200 and says OutOfStock, and it says InStock again when you restock it. Deleting it makes the address answer 404, and a product created later is a new page. Delete or trash a product only when it is gone for good.

Does WooCommerce add noindex to a product that is out of stock?

No. The robots tag on the product's page is the same before and after, and the product stays in the sitemap. Ticking "Hide out of stock items from the catalog" does not add one either. That box changes which of your own pages list the product.

Does "Hide out of stock items from the catalog" remove the product from Google?

It does nothing that asks for that. The product's own address still answers 200 and is still in the sitemap. The box takes the product out of the shop, category pages and product search on your own site. Whether Google keeps showing a page is Google's decision.

Should I redirect a discontinued product to the home page or to its category?

Not to the home page. Google's guide to site moves says a redirect to an irrelevant destination, with the home page as its example, might be treated as a soft 404. Its guidance is to redirect when a page has a clear replacement, and to answer 404 or 410 when it has none. A category that happens to hold similar products is a judgment call that Google's documentation does not settle.

What does the address of a product in the trash answer?

404, with the site's "Page not found" page, unless WordPress finds a published post whose slug begins the same way. Then it answers 301 to that post. Run the status command on this page to see which.

How long does Google take to drop a removed product?

Google gives no date. Its documentation says an address that answers 404 is removed from the index, and that Google stops using such an address over time. Search Console's help says Googlebot will probably keep trying a removed address for a while, less and less often.

More on this subject

SEO audit, done for you

SEO audit is $249. A written audit, each finding with its source, and up to 2 hours of fixes done and tested again. It starts with a free diagnosis.