WooCommerce shows out of stock products wherever they fall in the sorting, so a customer can scroll past a row of products they cannot buy before reaching ones they can. Hiding them completely loses the product pages and their search traffic.
This snippet shows out of stock products last in WooCommerce: on the shop, product category and tag pages and in product search. Inside each group the sorting stays whatever the customer chose: default, popularity, rating, latest or price.
The snippet
<?php
/**
* Show out of stock products after the products in stock, in every shop sorting.
*/
add_filter( 'posts_clauses', 'dpc_out_of_stock_last', 20, 2 );
function dpc_out_of_stock_last( $clauses, $query ) {
// Only the WooCommerce shop, product category, tag and product search pages.
if ( 'product_query' !== $query->get( 'wc_query' ) ) {
return $clauses;
}
global $wpdb;
$clauses['join'] .= " LEFT JOIN {$wpdb->postmeta} AS dpc_stock ON ( {$wpdb->posts}.ID = dpc_stock.post_id AND dpc_stock.meta_key = '_stock_status' )";
$stock_order = "CASE WHEN dpc_stock.meta_value = 'outofstock' THEN 1 ELSE 0 END ASC";
$clauses['orderby'] = '' === trim( $clauses['orderby'] ) ? $stock_order : $stock_order . ', ' . $clauses['orderby'];
return $clauses;
}
How it works
- WooCommerce marks its own catalogue query with the query var
wc_queryset toproduct_query. The snippet only touches that query, so admin lists, product blocks, widgets and the cart are not affected. - It joins the
_stock_statusvalue of each product and puts one rule in front of the existingORDER BY: out of stock products get 1, everything else 0. WooCommerce’s own sorting (price, popularity, title and so on) follows after it. - Products on backorder can still be bought, so they stay with the products in stock.
- The original snippet by Business Bloomer replaced the sorting with
orderby => meta_valueon the stock status. That overrode the customer’s choice in the sorting dropdown and only putoutofstocklast because it sorts afterinstockalphabetically.
Add it with Scripts Organizer
In Scripts Organizer go to Code Blocks → Add New and give the block a name. Set Trigger location to Everywhere, tick PHP under Script location, paste the code into the PHP editor and publish. Leave Action/Hook empty. The filter waits for the shop query, so it can load before WooCommerce.
The same code also works unchanged in a small plugin or an mu-plugin.
Customise it
- Backorders last too: change the condition to
dpc_stock.meta_value IN ( 'outofstock', 'onbackorder' ). - Hide out of stock products instead: WooCommerce has a setting for it in WooCommerce → Settings → Products → Inventory → Out of stock visibility.
- Change the label of the products that stay: see Woo : Change “Out of Stock” to SOLD OUT.
Gotchas
- The
[products]shortcode and the Product Collection block run their own queries, not the catalogue query, so they keep their own order. - Variable products have the stock status of the parent: a variable product counts as in stock while at least one variation is.
- Page caches keep the old order until they are cleared.
In the Scripts Organizer library Import it with one click from Scripts Organizer.