WooCommerce orders move through a lifecycle of statuses: pending → processing → completed (or cancelled, refunded, failed, on-hold). For most stores these built-in statuses are sufficient, but complex fulfilment workflows often need custom statuses — “awaiting supplier”, “partially shipped”, “ready for pickup”, “quality check”, and so on. WooCommerce stores order statuses as custom WordPress post statuses (the shop_order CPT), and adding a custom status requires: (1) registering it with register_post_status(), (2) telling WooCommerce to include it via the wc_order_statuses filter, (3) adding a colour and icon in the WooCommerce admin UI via CSS, and optionally (4) controlling which status transitions are allowed via the woocommerce_valid_order_statuses_for_* filters. Getting any step wrong results in statuses that appear in the dropdown but are invisible in the list, or that trigger the wrong email templates.
Problem: A furniture store needs a "Ready for Pickup" status that appears in the order admin list with a distinct colour, allows the store to email the customer using a custom email template, and allows the order to be moved to "Completed" afterwards.
Solution: Register the status with register_post_status(), add it to WooCommerce's list, style it with inline CSS, and register a custom order email class triggered by the status transition.
<?php
// ── 1. Register post status (must run early, before WC reads statuses) ─
add_action( 'init', function () {
register_post_status( 'wc-ready-pickup', [
'label' => _x( 'Ready for Pickup', 'Order status', 'textdomain' ),
'public' => true,
'show_in_admin_all_list' => true,
'show_in_admin_status_list' => true,
/* translators: %s = number of orders */
'label_count' => _n_noop( 'Ready for Pickup (%s)', 'Ready for Pickup (%s)', 'textdomain' ),
'exclude_from_search' => false,
] );
} );
// ── 2. Add to WooCommerce status list ─────────────────────────────────
add_filter( 'wc_order_statuses', function ( array $statuses ): array {
// Insert after 'wc-processing' in the list
$new = [];
foreach ( $statuses as $key => $label ) {
$new[ $key ] = $label;
if ( 'wc-processing' === $key ) {
$new['wc-ready-pickup'] = __( 'Ready for Pickup', 'textdomain' );
}
}
return $new;
} );
// ── 3. Style the status badge in admin ────────────────────────────────
add_action( 'admin_head', function () {
echo '<style>
.order-status.status-ready-pickup {
background: #e8f5e9;
color: #2e7d32;
}
</style>';
} );
// ── 4. Allow transition to/from the new status ────────────────────────
// Allow moving to completed from ready-pickup
add_filter( 'woocommerce_valid_order_statuses_for_payment_complete', function ( array $statuses ): array {
$statuses[] = 'ready-pickup'; // without 'wc-' prefix
return $statuses;
} );
// ── 5. Send email when order moves to ready-pickup ────────────────────
// The action woocommerce_order_status_{from}_to_{to} fires on every transition
add_action( 'woocommerce_order_status_processing_to_ready-pickup', function ( int $order_id ) {
$order = wc_get_order( $order_id );
if ( ! $order ) return;
$customer_email = $order->get_billing_email();
$subject = sprintf( __( 'Your order #%s is ready for pickup!', 'textdomain' ), $order->get_order_number() );
$message = sprintf(
__( 'Hello %s, your order is ready to collect from our store.', 'textdomain' ),
esc_html( $order->get_billing_first_name() )
);
wc_mail( $customer_email, $subject, $message );
} );
NOTE: WooCommerce internally strips the wc- prefix when storing the status in order functions — $order->get_status() returns 'ready-pickup', not 'wc-ready-pickup'. Use the prefix-less version in PHP comparisons and the transition hooks. The register_post_status() call must run on init, not later — WooCommerce reads statuses during its own initialisation. To include the custom status in WooCommerce reports (the analytics dashboard), add it to the woocommerce_reports_order_statuses filter. Custom order statuses do not automatically get dedicated email templates — you need to register a WC_Email subclass or use the simpler wc_mail() helper shown above for basic notifications.