WooCommerce Custom Order Status Generator

Register a new order status that works with both classic and HPOS order storage, with a bulk action and an email stub.

Describe the result you need. AI will set up the tool and ask for any missing details.

Configure with AI

Enter to submit · Shift + Enter for a new line

Sent to AI. Leave out passwords and private customer data.

Your details

Your snippet

woocommerce-order-status-backorder.php
<?php
/**
 * Register a custom WooCommerce order status: "Backorder" (wc-backorder).
 * HPOS-safe: register_post_status() and the wc_order_statuses filter work the same
 * whether High-Performance Order Storage is on or off.
 */

add_action( 'init', 'ea_register_order_status_backorder' );
function ea_register_order_status_backorder() {
	register_post_status( 'wc-backorder', array(
		'label'                     => _x( 'Backorder', 'Order status', 'woocommerce' ),
		'public'                    => false,
		'exclude_from_search'       => false,
		'show_in_admin_all_list'    => true,
		'show_in_admin_status_list' => true,
		'label_count'               => _n_noop( 'Backorder <span class="count">(%s)</span>', 'Backorder <span class="count">(%s)</span>', 'woocommerce' ),
	) );
}

add_filter( 'wc_order_statuses', 'ea_add_order_status_backorder' );
function ea_add_order_status_backorder( $order_statuses ) {
	$new_statuses = array();
	foreach ( $order_statuses as $status_key => $status_label ) {
		$new_statuses[ $status_key ] = $status_label;
		if ( 'wc-processing' === $status_key ) {
			$new_statuses['wc-backorder'] = _x( 'Backorder', 'Order status', 'woocommerce' );
		}
	}
	if ( ! isset( $new_statuses['wc-backorder'] ) ) {
		$new_statuses['wc-backorder'] = _x( 'Backorder', 'Order status', 'woocommerce' );
	}
	return $new_statuses;
}

add_filter( 'bulk_actions-edit-shop_order', 'ea_backorder_bulk_action' );
add_filter( 'bulk_actions-woocommerce_page_wc-orders', 'ea_backorder_bulk_action' );
function ea_backorder_bulk_action( $actions ) {
	$actions['mark_backorder'] = __( 'Change status to Backorder', 'woocommerce' );
	return $actions;
}

add_filter( 'handle_bulk_actions-edit-shop_order', 'ea_backorder_handle_bulk_action', 10, 3 );
add_filter( 'handle_bulk_actions-woocommerce_page_wc-orders', 'ea_backorder_handle_bulk_action', 10, 3 );
function ea_backorder_handle_bulk_action( $redirect_to, $action, $order_ids ) {
	if ( 'mark_backorder' !== $action ) {
		return $redirect_to;
	}
	foreach ( $order_ids as $order_id ) {
		$order = wc_get_order( $order_id );
		if ( $order ) {
			$order->update_status( 'backorder', __( 'Order status changed by bulk edit.', 'woocommerce' ), true );
		}
	}
	return add_query_arg( 'marked_backorder', count( $order_ids ), $redirect_to );
}

/**
 * Notify the customer when an order moves to "Backorder".
 */
add_action( 'woocommerce_order_status_backorder', 'ea_backorder_send_email_trigger', 10, 2 );
function ea_backorder_send_email_trigger( $order_id, $order ) {
	$email = WC()->mailer()->emails['WC_Email_Backorder'] ?? null;
	if ( $email ) {
		$email->trigger( $order_id, $order );
	}
}

add_filter( 'woocommerce_email_classes', 'ea_backorder_register_email_class' );
function ea_backorder_register_email_class( $email_classes ) {
	if ( ! class_exists( 'WC_Email_Backorder' ) ) {
		class WC_Email_Backorder extends WC_Email {
			public function __construct() {
				$this->id          = 'backorder_notification';
				$this->title       = __( 'Backorder', 'woocommerce' );
				$this->description = __( 'Sent to the customer when an order changes to Backorder.', 'woocommerce' );
				$this->customer_email = true;
				$this->template_html  = 'emails/customer-processing-order.php';
				$this->template_plain = 'emails/plain/customer-processing-order.php';
				$this->placeholders   = array( '{order_date}' => '', '{order_number}' => '' );
				parent::__construct();
				$this->recipient = '';
			}
			public function trigger( $order_id, $order = false ) {
				if ( $order_id && ! is_a( $order, 'WC_Order' ) ) {
					$order = wc_get_order( $order_id );
				}
				if ( ! $order ) {
					return;
				}
				$this->object                         = $order;
				$this->recipient                      = $order->get_billing_email();
				$this->placeholders['{order_date}']   = wc_format_datetime( $order->get_date_created() );
				$this->placeholders['{order_number}'] = $order->get_order_number();
				if ( ! $this->is_enabled() || ! $this->get_recipient() ) {
					return;
				}
				$this->send( $this->get_recipient(), $this->get_subject(), $this->get_content(), $this->get_headers(), $this->get_attachments() );
			}
			public function get_default_subject() {
				return 'Your {site_title} order is on backorder';
			}
			public function get_default_heading() {
				return 'Your order is on backorder';
			}
		}
	}
	$email_classes['WC_Email_Backorder'] = new WC_Email_Backorder();
	return $email_classes;
}

Every status change fires woocommerce_order_status_backorder (no wc- prefix), so any plugin can react to it the same way this email stub does.

bulk_actions-edit-shop_order covers classic order storage; bulk_actions-woocommerce_page_wc-orders covers HPOS. This snippet adds both, since only one will ever match your store's screen.

The email stub reuses the “Processing order” template as a starting point. Copy templates/emails/customer-processing-order.php into yourtheme/woocommerce/emails/ to customize its content, and set template_html to that path.

What a custom order status needs

  1. 01

    Register the status

    register_post_status() on init creates the wc-{key} status so WordPress and WooCommerce both recognize it, with a label and counts shown in the admin order list.

  2. 02

    Add it to the order statuses list

    The wc_order_statuses filter controls the dropdown on the order edit screen and the order list filters. Without this, the status exists but nobody can set it from the admin.

  3. 03

    Add it to bulk actions (optional)

    WooCommerce's order list has its own bulk action dropdown. Adding your status needs the bulk_actions-{screen} filter, and the screen ID differs between classic order storage (edit-shop_order) and HPOS (woocommerce_page_wc-orders), so the snippet checks which one is active.

  4. 04

    React to the change (optional)

    Every status change fires woocommerce_order_status_{new_status}, with no wc- prefix. A stub email class hooks that action to send a notification, the same pattern WooCommerce's own status emails use.

This works the same whether High-Performance Order Storage (HPOS) is on or off: register_post_status() and the wc_order_statuses filter aren't affected by where orders are stored, only the admin screen ID for bulk actions changes.

Adding it to your store

Paste the snippet into a small custom plugin (not just functions.php) if other plugins or your payment gateway need to check for this status, since a theme switch would otherwise remove it. Then go to WooCommerce → Orders, open an order and confirm the new status appears in the status dropdown.

A status you remove later will leave existing orders stuck on it. Either bulk-change those orders to another status first, or keep the registration in place even if you stop actively using the status.

Questions people ask

Do I need to do anything different for HPOS?

No extra registration step. The only HPOS-specific part is which admin screen ID the bulk-action filter targets, and this snippet detects that automatically with OrderUtil::custom_orders_table_usage_is_enabled().

Will payment gateways understand my new status?

No. Gateways and other plugins only react to the statuses they know about (usually processing, completed, on-hold, refunded). A custom status is a manual or workflow status; don't use it for anything a gateway needs to trigger automatically, like marking payment as captured.

Can I set this as the default status for new orders?

Not directly; new orders start as pending or processing depending on the payment method. You'd transition orders into your custom status afterward, either manually or from a hook on an existing status change.

Why does the order count on my new status stay at 0?

show_in_admin_status_list and show_in_admin_all_list need to be true (this snippet sets both) for WordPress to include the status in its counts. If you still see 0, an object cache or transient may need clearing; WooCommerce's Status page has a “Clear system status theme info cache” style tool for some caches, or just wait a minute for the counts to refresh.

Does the bulk action work on the Trash view too?

No, this snippet only adds the action to the normal order list view, matching how WooCommerce's own status bulk actions behave.

Want this builtinto your store?

I'm Emmanuel, a Shopify and WooCommerce developer. If this tool pointed at something you'd rather have fixed for you (a faster store, cleaner product data, a migration, a custom feature), send me a note.

Emmanuel Asika

Emmanuel Asika

Shopify & WooCommerce developer, Dublin

I build and fix online stores: new builds, migrations, speed, SEO and the custom bits apps can't do.

hello@emmanuelasika.com