How to add custom stock status to products in WooCommerce 4+
Last update: 04/22 - Tested in WordPress 5.9.2 & WooCommerce 6.3.1
Code goes in functions.php file of the active child theme (or active theme).
Use
woocommerce_product_stock_status_options
instead of
woocommerce_product_options_stock_status
.This way you can immediately add a status instead of replace the existing dropdown
Also use
woocommerce_get_availability_text
&woocommerce_get_availability_class
oppositewoocommerce_get_availability
.This way you don't have to add the existing statuses again
// Add new stock status options
function filter_woocommerce_product_stock_status_options( $status ) {
// Add new statuses
$status['pre_order'] = __( 'Pre order', 'woocommerce' );
$status['contact_us'] = __( 'Contact us', 'woocommerce' );
return $status;
}
add_filter( 'woocommerce_product_stock_status_options', 'filter_woocommerce_product_stock_status_options', 10, 1 );
// Availability text
function filter_woocommerce_get_availability_text( $availability, $product ) {
// Get stock status
switch( $product->get_stock_status() ) {
case 'pre_order':
$availability = __( 'Pre order', 'woocommerce' );
break;
case 'contact_us':
$availability = __( 'Contact us', 'woocommerce' );
break;
}
return $availability;
}
add_filter( 'woocommerce_get_availability_text', 'filter_woocommerce_get_availability_text', 10, 2 );
// Availability CSS class
function filter_woocommerce_get_availability_class( $class, $product ) {
// Get stock status
switch( $product->get_stock_status() ) {
case 'pre_order':
$class = 'pre-order';
break;
case 'contact_us':
$class = 'contact-us';
break;
}
return $class;
}
add_filter( 'woocommerce_get_availability_class', 'filter_woocommerce_get_availability_class', 10, 2 );
Use
woocommerce_admin_stock_html
to display the new stock status on the admin products list table
// Admin stock html
function filter_woocommerce_admin_stock_html( $stock_html, $product ) {
// Simple
if ( $product->is_type( 'simple' ) ) {
// Get stock status
$product_stock_status = $product->get_stock_status();
// Variable
} elseif ( $product->is_type( 'variable' ) ) {
foreach( $product->get_visible_children() as $variation_id ) {
// Get product
$variation = wc_get_product( $variation_id );
// Get stock status
$product_stock_status = $variation->get_stock_status();
/*
Currently the status of the last variant in the loop will be displayed.
So from here you need to add your own logic, depending on what you expect from your custom stock status.
By default, for the existing statuses. The status displayed on the admin products list table for variable products is determined as:
- Product should be in stock if a child is in stock.
- Product should be out of stock if all children are out of stock.
- Product should be on backorder if all children are on backorder.
- Product should be on backorder if at least one child is on backorder and the rest are out of stock.
*/
}
}
// Stock status
switch( $product_stock_status ) {
case 'pre_order':
$stock_html = '<mark class="pre-order" style="background:transparent none;color:#33ccff;font-weight:700;line-height:1;">' . __( 'Pre order', 'woocommerce' ) . '</mark>';
break;
case 'contact_us':
$stock_html = '<mark class="contact-us" style="background:transparent none;color:#cc33ff;font-weight:700;line-height:1;">' . __( 'Contact us', 'woocommerce' ) . '</mark>';
break;
}
return $stock_html;
}
add_filter( 'woocommerce_admin_stock_html', 'filter_woocommerce_admin_stock_html', 10, 2 );
Optional: if desired, the custom stock status can be used in hooks where you already have access to the
$product
object or you can useglobal $product
.
1) No access to the $product
object, use global $product
as is the case for example with the woocommerce_shop_loop_item_title
or the woocommerce_single_product_summary
hook
function woocommerce_my_callback() {
// An example based on global $product
// Get the global product object
global $product;
// Is a WC product
if ( is_a( $product, 'WC_Product' ) ) {
// Get stock status
$product_stock_status = $product->get_stock_status();
// Use this line during testing, delete afterwards!
echo '<p style="color:red;font-size:20px;">DEBUG INFORMATION = ' . $product_stock_status . '</p>';
// Compare
if ( $product_stock_status == 'My custom stock status' ) {
// Etc..
}
}
}
add_action( 'woocommerce_shop_loop_item_title', 'woocommerce_my_callback', 10 );
add_action( 'woocommerce_single_product_summary', 'woocommerce_my_callback', 10 );
2) Access to the $product
object, because it's passed by default to the callback function. As is the case for example with the woocommerce_get_price_html
hook
function filter_woocommerce_get_price_html( $price, $product ) {
// Is a WC product
if ( is_a( $product, 'WC_Product' ) ) {
// Get stock status
$product_stock_status = $product->get_stock_status();
// Use this line during testing, delete afterwards!
echo '<p style="color:red;font-size:20px;">DEBUG INFORMATION = ' . $product_stock_status . '</p>';
// Compare
if ( $product_stock_status == 'My custom stock status' ) {
// Etc..
// $price .= ' my text';
}
}
return $price;
}
add_filter( 'woocommerce_get_price_html', 'filter_woocommerce_get_price_html', 10, 2 );
Add custom stock status in WooCommerce treated as out of stock
Since you indicate in your question that your custom stock statuses should contain the same functionality as the current 'outofstock' status, you can instead of rewrite/reuse all existing functionality of the 'outofstock' status for your custom stock statuses, use a workaround.
This will offer a simple solution, since otherwise you would have to overwrite some template files in addition to writing custom code.
The workaround can be applied as follows:
Step 1) add an extra custom field for your custom statuses, we will add this field under the existing stock status field:
// Add custom field
function action_woocommerce_product_options_stock_status() {
// Custom stock status
$options = array(
'empty' => __( 'N/A', 'woocommerce' ),
'permanent' => __( 'Permanent', 'woocommerce' ),
'supplier' => __( 'Supplier', 'woocommerce' ),
);
woocommerce_wp_select(
array(
'id' => '_custom_stock_status',
'wrapper_class' => 'stock_status_field hide_if_variable hide_if_external hide_if_grouped',
'label' => __( 'Custom stock status', 'woocommerce' ),
'options' => $options,
'desc_tip' => true,
'description' => __( 'Your description', 'woocommerce' ),
)
);
}
add_action( 'woocommerce_product_options_stock_status', 'action_woocommerce_product_options_stock_status', 10 );
// Save custom field
function action_woocommerce_admin_process_product_object( $product ) {
// Isset
if ( isset( $_POST['_custom_stock_status'] ) ) {
// Update
$product->update_meta_data( '_custom_stock_status', sanitize_text_field( $_POST['_custom_stock_status'] ) );
}
}
add_action( 'woocommerce_admin_process_product_object', 'action_woocommerce_admin_process_product_object', 10, 1 );
Result:
Step 2) when the product is 'out of stock', we will check whether a custom stock status has been set. If this is the case, we will visually change the text using the code below, so that the customer can see the new status. In the background/backend, however, the 'out of stock' status is still used and therefore automatically also the existing functionality:
// Availability text
function filter_woocommerce_get_availability_text( $availability, $product ) {
// Only for 'outofstock'
if ( $product->get_stock_status() == 'outofstock' ) {
// Get custom stock status
$custom_stock_status = $product->get_meta( '_custom_stock_status' );
// Compare and apply new text
if ( $custom_stock_status == 'permanent' ) {
$availability = __( 'Out of stock (Permanent)', 'woocommerce' );
} elseif ( $custom_stock_status == 'supplier' ) {
$availability = __( 'Out of stock (Supplier)', 'woocommerce' );
}
}
return $availability;
}
add_filter( 'woocommerce_get_availability_text', 'filter_woocommerce_get_availability_text', 10, 2 );
Add a custom stock status in WooCommerce
for anyone interested, here is complete solution, based on Laila's approach. Warning! My solution is intended to work only with WooCommerce "manage stock" option disabled! I am not working with exact amounts of items in stock. All code goes to functions.php
, as usual.
Back-end part
Removing native stock status dropdown field. Adding CSS class to distinguish my new custom field. Dropdown has now new option "On Request".
function add_custom_stock_type() {
?>
<script type="text/javascript">
jQuery(function(){
jQuery('._stock_status_field').not('.custom-stock-status').remove();
});
</script>
<?php
woocommerce_wp_select( array( 'id' => '_stock_status', 'wrapper_class' => 'hide_if_variable custom-stock-status', 'label' => __( 'Stock status', 'woocommerce' ), 'options' => array(
'instock' => __( 'In stock', 'woocommerce' ),
'outofstock' => __( 'Out of stock', 'woocommerce' ),
'onrequest' => __( 'On Request', 'woocommerce' ), // The new option !!!
), 'desc_tip' => true, 'description' => __( 'Controls whether or not the product is listed as "in stock" or "out of stock" on the frontend.', 'woocommerce' ) ) );
}
add_action('woocommerce_product_options_stock_status', 'add_custom_stock_type');
Sadly, WooCommerce will save only "instock" or "outofstock" values with its native functions. So after all product data processing, I have to re-save my stock status again.
function save_custom_stock_status( $product_id ) {
update_post_meta( $product_id, '_stock_status', wc_clean( $_POST['_stock_status'] ) );
}
add_action('woocommerce_process_product_meta', 'save_custom_stock_status',99,1);
Template part
And the last thing - I have to alter data returned by product get_availability()
function. When "managing stock" is off, WooCommerce only knows "instock" and "outofstock" values, again. So I have check stock status on my own.
function woocommerce_get_custom_availability( $data, $product ) {
switch( $product->stock_status ) {
case 'instock':
$data = array( 'availability' => __( 'In stock', 'woocommerce' ), 'class' => 'in-stock' );
break;
case 'outofstock':
$data = array( 'availability' => __( 'Out of stock', 'woocommerce' ), 'class' => 'out-of-stock' );
break;
case 'onrequest':
$data = array( 'availability' => __( 'On request', 'woocommerce' ), 'class' => 'on-request' );
break;
}
return $data;
}
add_action('woocommerce_get_availability', 'woocommerce_get_custom_availability', 10, 2);
Maybe it's not bulletproof solution ... I will update it, eventually.
Display custom stock status based on stock quantity in admin products list table
If you like to display the custom stock status on the admin products list table, based on the current stock quantity, you can just use:
// Admin stock html
function filter_woocommerce_admin_stock_html( $stock_html, $product ) {
if ( $product->get_stock_quantity() == 1 ) {
$stock_html = '<mark class="my-class" style="background:transparent none;color:#33ccff;font-weight:700;line-height:1;">' . __( 'Available soon', 'woocommerce' ) . '</mark>';
}
return $stock_html;
}
add_filter( 'woocommerce_admin_stock_html', 'filter_woocommerce_admin_stock_html', 10, 2 );
Related: How to add custom stock status to products in WooCommerce 4+
WooCommerce - Custom stock status and variable products
Unfortunately WooCommerce not supporting any custom stock status other than its own stock statuses - instock, outofstock & onbackorder. We can achieve the custom stock status by overriding the
_stock_status
meta in some cases but will not be successful in all cases (like variations update).
In the above functions, you have removed the onbackorder stock status and if particular product stock status changed to 'onbackorder', then it will not be shown. Note that this is the reason for showing 'instock' since its the first option in your select box.
If you are going to use the stock status purely for admin panel display purpose only, then you can achieve this by using the following functions.
/* add custom stock status */
function woocommerce_add_custom_stock_status() {
?>
<script type="text/javascript">
jQuery(function(){
jQuery('._stock_status_field').not('.custom-stock-status').remove();
});
</script>
<?php
/* update custom status if backorder if varations updated */
$real_stock_status = get_post_meta($_REQUEST['post'], '_stock_status', true );
if($real_stock_status=="onbackorder") {
$stock_status = get_post_meta($_REQUEST['post'], '_custom_stock_status', true ); //get status from custom meta
update_post_meta($_REQUEST['post'], '_stock_status', wc_clean( $stock_status ));
}
woocommerce_wp_select( array( 'id' => '_stock_status', 'wrapper_class' => 'custom-stock-status', 'label' => __( 'Stock status', 'woocommerce' ), 'options' => array(
'instock' => __( 'På lager/fjernlager', 'woocommerce' ),
'bestillingsvare' => __( 'Bestillingsvare', 'woocommerce' ), // The new option !!!
'outofstock' => __( 'Ikke på lager', 'woocommerce' ),
), 'desc_tip' => true, 'description' => __( 'Controls whether or not the product is listed as "in stock" or "out of stock" on the frontend.', 'woocommerce' ) ) );
}
add_action('woocommerce_product_options_stock_status', 'woocommerce_add_custom_stock_status');
/* save custom stock status */
function woocommerce_save_custom_stock_status( $product_id ) {
update_post_meta( $product_id, '_stock_status', wc_clean( $_POST['_stock_status'] ) );
update_post_meta( $product_id, '_custom_stock_status', wc_clean( $_POST['_stock_status'] ) ); //save another custom meta since '_stock_status' will be overridden
}
add_action('woocommerce_process_product_meta', 'woocommerce_save_custom_stock_status',99,1);
/* get custom stock status */
function get_custom_stock_status( $data, $product ) {
switch( $product->stock_status ) {
case 'instock':
$data = array( 'availability' => __( 'På lager/fjernlager', 'woocommerce' ), 'class' => 'in-stock' );
break;
case 'bestillingsvare':
$data = array( 'availability' => __( 'Bestillingsvare', 'woocommerce' ), 'class' => 'bestillings-vare' );
break;
case 'outofstock':
$data = array( 'availability' => __( 'Ikke på lager', 'woocommerce' ), 'class' => 'out-of-stock' );
break;
}
return $data;
}
add_action('woocommerce_get_availability', 'get_custom_stock_status', 10, 2);
/* change custom stock status after order completion */
function woocommerce_order_change_custom_stock_status( $order_id ){
if( ! $order_id ) return;
$order = wc_get_order( $order_id );
$items = $order->get_items();
foreach ( $items as $item ) {
$product_id = $item->get_product_id();
$real_stock_status = get_post_meta($product_id, '_stock_status', true );
if($real_stock_status=="onbackorder") {
$stock_status = get_post_meta($product_id, '_custom_stock_status', true ); //get status from custom meta
update_post_meta($product_id, '_stock_status', wc_clean( $stock_status ));
}
}
}
add_action( 'woocommerce_thankyou', 'woocommerce_order_change_custom_stock_status', 10, 1 );
Hope this helps.
Display product stock status on single product pages in WooCommerce when price field is empty
You will see that if the price is empty, besides not showing the product stock status, the price (the HTML code) for this will also not be displayed. Because the product is considered as not purchasable if the price is empty.
So in order to display the product stock status, you should make the product purchasable, if the price is empty. However, I do not believe this is your intention?
So instead of focusing on displaying the product stock status, it will be much simpler to display something else (HTML/text) where the price is normally displayed,
and that can be done by using the woocommerce_empty_price_html
filter hook
- So either you drop the whole idea of the product stock status and simply use:
function filter_woocommerce_empty_price_html( $html, $product ) {
// NOT true on a single product page, RETURN
if ( ! is_product() ) return $html;
// Add HTML
$html = '<p>My text</p>';
return $html;
}
add_filter( 'woocommerce_empty_price_html', 'filter_woocommerce_empty_price_html', 10, 2 );
- Or you build further on the product stock status and on that basis you get:
function filter_woocommerce_empty_price_html( $html, $product ) {
// NOT true on a single product page, RETURN
if ( ! is_product() ) return $html;
// Get stock status
$product_stock_status = $product->get_stock_status();
// Compare
if ( $product_stock_status == 'MY CUSTOM STATUS' ) {
// Add HTML
$html = '<p>My text based on product stock status</p>';
}
return $html;
}
add_filter( 'woocommerce_empty_price_html', 'filter_woocommerce_empty_price_html', 10, 2 );
The only downside to this solution(s) is that your new HTML/text will not be displayed where you normally see the product stock status, but where the price is normally displayed.
Disable add to cart button based on WooCommerce product custom stock status
You can use the following to disable add to cart button based on a custom stock status (where you will replace custom_status_slug
by your custom status slug):
add_filter('woocommerce_is_purchasable', 'filter_is_purchasable_callback', 10, 2 );
add_filter('woocommerce_variation_is_purchasable', 'filter_is_purchasable_callback', 10, 2 );
function filter_is_purchasable_callback( $purchasable, $product ) {
if ( $product->get_stock_status() === 'custom_status_slug' ) {
return false;
}
return $purchasable;
}
Code goes in functions.php file of the active child theme (or active theme). Tested and works.
Related Topics
How to Avoid Echoing Character 65279 in PHP
"Unknown Modifier 'G' In..." When Using Preg_Match in PHP
Notice: Trying to Get Property of Non-Object Error
Find Highest Value in Multidimensional Array
How to Remove All Specific Characters At the End of a String in PHP
Fpdf Error: Some Data Has Already Been Output, Can't Send Pdf
Trying to Get Property of Non-object In
How to Login in With Curl and Ssl and Cookies
What Does "1" Mean At the End of a PHP Print_R Statement
Remove the Last Character from a String
Accurate Way to Measure Execution Times of PHP Scripts
Who Should Handle the Conditions in Complex Queries, the Data Mapper or the Service Layer
PHP Library For Parsing Xml With a Colons in Tag Names
Difference Between a Language Construct and a "Built-In" Function in PHP
PHP Explode the String, But Treat Words in Quotes as a Single Word