Commit ae9c01e9f6e for woocommerce
commit ae9c01e9f6e9c122b02ebc3d427dcdd7b438513a
Author: Michael Pretty <prettyboymp@users.noreply.github.com>
Date: Mon Apr 13 06:24:43 2026 -0400
Exclude checkout-draft orders from default REST API order queries (#63743)
* Exclude checkout-draft orders from default API order queries
Set exclude_from_search to true for the checkout-draft post status and
teach HPOS OrdersTableQuery to respect this attribute when resolving
status=any, aligning it with WP_Query's existing behavior.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add changefile(s) from automation for the following project(s): woocommerce
* Add changefile(s) from automation for the following project(s): woocommerce
* Update changelog to minor/update and fix lint alignment warning
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Address review findings: CLOG-2, CMT-2
- CLOG-2: Broaden changelog description to reflect full scope (HPOS, WP_Query, REST API)
- CMT-2: Fix test comment to clarify both checkout-draft and auto-draft use same exclusion mechanism (exclude_from_search=true)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Add changefile(s) from automation for the following project(s): woocommerce
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: woocommercebot <woocommercebot@users.noreply.github.com>
diff --git a/plugins/woocommerce/changelog/63743-fix-exclude-checkout-draft-from-api-any-status b/plugins/woocommerce/changelog/63743-fix-exclude-checkout-draft-from-api-any-status
new file mode 100644
index 00000000000..ef86fba57c2
--- /dev/null
+++ b/plugins/woocommerce/changelog/63743-fix-exclude-checkout-draft-from-api-any-status
@@ -0,0 +1,4 @@
+Significance: minor
+Type: update
+
+Exclude checkout-draft orders from default REST API order queries (status=any). API consumers that rely on checkout-draft orders appearing in unfiltered order listings will need to explicitly request them via `status=checkout-draft`.
\ No newline at end of file
diff --git a/plugins/woocommerce/src/Blocks/Domain/Services/DraftOrders.php b/plugins/woocommerce/src/Blocks/Domain/Services/DraftOrders.php
index a8b05145455..11431f1b822 100644
--- a/plugins/woocommerce/src/Blocks/Domain/Services/DraftOrders.php
+++ b/plugins/woocommerce/src/Blocks/Domain/Services/DraftOrders.php
@@ -122,7 +122,7 @@ class DraftOrders {
return [
'label' => _x( 'Draft', 'Order status', 'woocommerce' ),
'public' => false,
- 'exclude_from_search' => false,
+ 'exclude_from_search' => true,
'show_in_admin_all_list' => false,
'show_in_admin_status_list' => true,
/* translators: %s: number of orders */
diff --git a/plugins/woocommerce/src/Internal/DataStores/Orders/OrdersTableQuery.php b/plugins/woocommerce/src/Internal/DataStores/Orders/OrdersTableQuery.php
index 5e9e988f251..b17c6c07d04 100644
--- a/plugins/woocommerce/src/Internal/DataStores/Orders/OrdersTableQuery.php
+++ b/plugins/woocommerce/src/Internal/DataStores/Orders/OrdersTableQuery.php
@@ -698,8 +698,10 @@ class OrdersTableQuery {
}
if ( empty( $this->args['status'] ) || in_array( 'any', $this->args['status'], true ) ) {
- // Querying for 'any' status or empty status, filter to valid statuses from wc_get_order_statuses().
- $this->args['status'] = $valid_statuses;
+ // Querying for 'any' status or empty status, filter to valid statuses from wc_get_order_statuses(),
+ // excluding statuses marked as exclude_from_search (e.g. checkout-draft) to match WP_Query behavior.
+ $exclude = get_post_stati( array( 'exclude_from_search' => true ) );
+ $this->args['status'] = array_diff( $valid_statuses, $exclude );
} elseif ( in_array( 'all', $this->args['status'], true ) ) {
// Querying for 'all' status does not filter by status at all.
$this->args['status'] = array();
diff --git a/plugins/woocommerce/tests/php/src/Internal/DataStores/Orders/OrdersTableDataStoreTests.php b/plugins/woocommerce/tests/php/src/Internal/DataStores/Orders/OrdersTableDataStoreTests.php
index 4d09ff7d147..8a033bb6006 100644
--- a/plugins/woocommerce/tests/php/src/Internal/DataStores/Orders/OrdersTableDataStoreTests.php
+++ b/plugins/woocommerce/tests/php/src/Internal/DataStores/Orders/OrdersTableDataStoreTests.php
@@ -722,13 +722,13 @@ class OrdersTableDataStoreTests extends \HposTestCase {
$order_checkout_draft->set_status( 'checkout-draft' );
$order_checkout_draft->save();
- // Test 'status' => 'any' - should return only valid WooCommerce statuses (excludes internal WordPress statuses like auto-draft).
+ // Test 'status' => 'any' - should return only valid WooCommerce statuses, excluding statuses with exclude_from_search=true (checkout-draft, auto-draft).
$query = new OrdersTableQuery( array( 'status' => 'any' ) );
- $this->assertEquals( 4, count( $query->orders ), "status='any' should return only valid WooCommerce statuses" );
+ $this->assertEquals( 3, count( $query->orders ), "status='any' should return only merchant-facing WooCommerce statuses" );
$this->assertContains( $order_pending->get_id(), $query->orders, "status='any' should include pending orders" );
$this->assertContains( $order_processing->get_id(), $query->orders, "status='any' should include processing orders" );
$this->assertContains( $order_completed->get_id(), $query->orders, "status='any' should include completed orders" );
- $this->assertContains( $order_checkout_draft->get_id(), $query->orders, "status='any' should include checkout-draft orders (registered WC status)" );
+ $this->assertNotContains( $order_checkout_draft->get_id(), $query->orders, "status='any' should exclude checkout-draft orders (exclude_from_search)" );
$this->assertNotContains( $order_auto_draft->get_id(), $query->orders, "status='any' should exclude auto-draft orders (internal WordPress status)" );
// Test 'status' => 'all' - should return all statuses without filtering.
@@ -751,24 +751,24 @@ class OrdersTableDataStoreTests extends \HposTestCase {
// Test with array of statuses including 'any'.
$query = new OrdersTableQuery( array( 'status' => array( 'any' ) ) );
- $this->assertEquals( 4, count( $query->orders ), "status=['any'] should work same as status='any'" );
+ $this->assertEquals( 3, count( $query->orders ), "status=['any'] should work same as status='any'" );
// Test empty status (should behave like 'any') - historical and WP_Query like behavior.
$query = new OrdersTableQuery( array( 'status' => '' ) );
- $this->assertEquals( 4, count( $query->orders ), "Empty status should behave like 'any' and return only valid WooCommerce statuses" );
+ $this->assertEquals( 3, count( $query->orders ), "Empty status should behave like 'any' and return only merchant-facing WooCommerce statuses" );
$this->assertContains( $order_pending->get_id(), $query->orders, 'Empty status should include pending orders' );
$this->assertContains( $order_processing->get_id(), $query->orders, 'Empty status should include processing orders' );
$this->assertContains( $order_completed->get_id(), $query->orders, 'Empty status should include completed orders' );
- $this->assertContains( $order_checkout_draft->get_id(), $query->orders, 'Empty status should include checkout-draft orders (registered WC status)' );
+ $this->assertNotContains( $order_checkout_draft->get_id(), $query->orders, 'Empty status should exclude checkout-draft orders (exclude_from_search)' );
$this->assertNotContains( $order_auto_draft->get_id(), $query->orders, 'Empty status should exclude auto-draft orders (internal WordPress status)' );
// Test omitted status (should behave like 'any').
$query = new OrdersTableQuery( array() );
- $this->assertEquals( 4, count( $query->orders ), "Omitted status should behave like 'any' and return only valid WooCommerce statuses" );
+ $this->assertEquals( 3, count( $query->orders ), "Omitted status should behave like 'any' and return only merchant-facing WooCommerce statuses" );
$this->assertContains( $order_pending->get_id(), $query->orders, 'Omitted status should include pending orders' );
$this->assertContains( $order_processing->get_id(), $query->orders, 'Omitted status should include processing orders' );
$this->assertContains( $order_completed->get_id(), $query->orders, 'Omitted status should include completed orders' );
- $this->assertContains( $order_checkout_draft->get_id(), $query->orders, 'Omitted status should include checkout-draft orders (registered WC status)' );
+ $this->assertNotContains( $order_checkout_draft->get_id(), $query->orders, 'Omitted status should exclude checkout-draft orders (exclude_from_search)' );
$this->assertNotContains( $order_auto_draft->get_id(), $query->orders, 'Omitted status should exclude auto-draft orders (internal WordPress status)' );
}