Add a Custom Order Status in WooCommerce

In this tutorial, we’re going to dive into WooCommerce custom order statuses.

In the first part of the tutorial, I will show you how to add a new order status in WooCommerce; in the second part, we’ll add it to WordPress bulk actions as well, so you can change order statuses for multiple orders at the same time.

One more thing: we’re going to work with custom order statuses programmatically only because I am not a big fan of having an excessive number of WordPress plugins on a single site, especially when it is just enough to deal with a couple of lines of code.

It is also worth noting that my Order Sync and Multisite Order Sync plugins work great with any custom order statuses.

Add a Custom Order Status Programmatically

When registering a status, we are using register_post_status() function, which makes us think that WooCommerce orders are custom post types and WooCommerce order statuses are just post statuses (of course, before HPOS orders appeared). I could prove that with a screenshot of the WordPress database if you wish. Under the post_status column for “orders post type,” you can find statuses like wc-pending, wc-processing, wc-on-hold, wc-completed, wc-cancelled, wc-refunded, wc-failed.

Enough chat; let’s create a custom order status now.

/*
 * WooCommerce Custom Order Status
 * @author Misha Rudrastyh
 * @url https://rudrastyh.com/woocommerce/order-statuses.html#add-custom-order-status
 */
add_action( 'init', 'rudr_register_awaiting_shipping_status' );

function rudr_register_awaiting_shipping_status() {

	register_post_status(
		'wc-misha-shipping',
		array(
			'label'		=> 'Awaiting shipping',
			'public'	=> true,
			'show_in_admin_status_list' => true,
			'label_count'	=> _n_noop( 'Awaiting shipping (%s)', 'Awaiting shipping (%s)' )
		)
	);

}

// Add registered status to list of WC Order statuses
add_filter( 'wc_order_statuses', 'rudr_add_status_to_list' );

function rudr_add_status_to_list( $order_statuses ) {

	$order_statuses[ 'wc-misha-shipping' ] = 'Awaiting shipping';
	return $order_statuses;

}

As a result, you will immediately find the new order status on the edit order page.

add a custom order status in WooCommerce

Now, let’s take a look at the register_post_status() function parameters.

register_post_status(
	'wc-misha-shipping',
	array(
		'label' => 'Awaiting shipping',
		'public'	=> true,
		'show_in_admin_status_list' => true,
		'label_count'	=> _n_noop( 'Awaiting shipping (%s)', 'Awaiting shipping (%s)' )
	)
);
show_in_admin_status_list

Everything is clear with the register_post_status() function, right? Let’s jump to the next part of the code.

Filter hook wc_order_statuses allows you to change the array of order statuses before they are displayed anywhere on the website. It also allows you to remove any of the default order statuses, which is not recommended and can be done only with caution. Or you can change their order!

For example, if you would like our custom status to be displayed right before the “Completed” status, you can use this code:

function rudr_add_status_to_list( $order_statuses ) {

	$new = array();

	foreach( $order_statuses as $id => $label ) {
		
		if( 'wc-completed' === $id ) { // before "Completed" status
			$new[ 'wc-misha-shipping' ] = 'Awaiting shipping';
		}
		
		$new[ $id ] = $label;

	}

	return $new;
	
}

You can also use array_splice() and array_merge() functions, if you want.

change the custom order status ordering

Add a Custom WooCommerce Order Status to Bulk Actions

It is time to make our custom order status work with the bulk actions dropdown.

To add anything to the bulk actions dropdown, we need to use bulk_action-{screen id}. A little more details about it is in the bulk actions tutorial.

But now, when we are going to use it for the orders page, its screen IDs are going to be:

Since the callback function is going to be the same, I recommend to use both hooks: bulk_action-edit-shop_order and bulk_actions-woocommerce_page_wc-orders.

// Add custom order status to the bulk actions dropdown
// CPT-based orders
add_filter( 'bulk_actions-edit-shop_order', 'rudr_register_bulk_action' );
// HPOS orders
add_filter( 'bulk_actions-woocommerce_page_wc-orders', 'rudr_register_bulk_action' );

function rudr_register_bulk_action( $bulk_actions ) {

	$bulk_actions[ 'mark_awaiting_shipping' ] = 'Change status to awaiting shipping'; // <option value="mark_awaiting_shipping">Change status to awaiting shipping</option>
	return $bulk_actions;

}

Once you paste this code to your theme functions.php file (I hope you know when to use or not use child themes), the option will appear in the bulk actions dropdown.

add a custom order status to the bulk actions dropdown

But at this moment, it is useless. I mean, it does nothing when you select it from the dropdown list and push the apply button.

In order to make it work, we have to use another filter hook, which is handle_bulk_actions-{screen id}, so for WooCommerce orders, we are using:

Here you go:

add_action( 'handle_bulk_actions-edit-shop_order', 'rudr_bulk_process_custom_status', 20, 3 );
add_action( 'handle_bulk_actions-woocommerce_page_wc-orders', 'rudr_bulk_process_custom_status', 20, 3 );

function rudr_bulk_process_custom_status( $redirect, $doaction, $object_ids ) {

	if( 'mark_awaiting_shipping' === $doaction ) {

		// change status of every selected order
		foreach ( $object_ids as $order_id ) {
			$order = wc_get_order( $order_id );
			$order->update_status( 'wc-misha-shipping' );
		}

		// do not forget to add query args to URL because we will show notices later
		$redirect = add_query_arg(
			array(
				'bulk_action' => 'marked_awaiting_shipping',
				'changed' => count( $object_ids ),
			),
			$redirect
		);

	}

	return $redirect;

}

On line 10, we are using a slug of a custom order status we created previously. It can be used with or without wc- prefix, by the way, $order->update_status( 'misha-shipping' ) is also ok.

Last but not least, let’s create admin notices.

add_action( 'admin_notices', function() {

	if( 
		isset( $_REQUEST[ 'bulk_action' ] ) 
		&& 'marked_awaiting_shipping' == $_REQUEST[ 'bulk_action' ]
	 	&& isset( $_REQUEST[ 'changed' ] )
		&& $_REQUEST[ 'changed' ]
	) {

		// displaying the message
		printf(
			'<div id="message" class="updated notice is-dismissible"><p>' . _n( '%d order status changed.', '%d order statuses changed.', $_REQUEST[ 'changed' ] ) . '</p></div>',
			$_REQUEST[ 'changed' ]
		);
		
	}

} );

Works perfectly.

WooCommerce bulk change custom order status
Misha Rudrastyh

Misha Rudrastyh

Hey guys and welcome to my website. For more than 15 years I've been doing my best to share with you some superb WordPress guides and tips for free.

Need some developer help? Contact me

Follow me on X