WooCommerce allows store administrators to delete orders directly from the WordPress dashboard. However, deleting a large number of orders manually can take time, especially when you need to remove old test orders or clean up outdated records.
You can automate this process using WooCommerce’s built-in order functions. For example, you might want to permanently delete orders created before the current year while keeping all orders from the current year.
In this tutorial, you will learn how to delete old WooCommerce orders programmatically using PHP. The example uses WooCommerce order functions instead of directly deleting database records, making it compatible with both traditional order storage and High-Performance Order Storage (HPOS).
1. Before Deleting WooCommerce Orders
Deleting orders permanently is a destructive operation. Before running any deletion code, keep the following points in mind:
- Take a complete backup of your WordPress database and website files.
- Test the code on a staging website before using it on a live store.
- Confirm the date condition so that you do not delete orders you need to retain.
- Remember that deleted orders may contain customer, payment, tax, and reporting information.
- Check whether your business needs to retain order records for accounting, tax, or legal purposes.
The example below permanently deletes matching orders rather than moving them to the trash. Make sure this is what you intend before proceeding.
2. Get WooCommerce Orders Created Before the Current Year
WooCommerce provides the wc_get_orders() function to retrieve orders using different conditions, including their creation date.
To find orders created before January 1 of the current year, you can use the following code:
$start_of_year = wp_date( 'Y-01-01' );
$orders = wc_get_orders(
array(
'limit' => 100,
'date_created' => '<' . strtotime( $start_of_year ),
'return' => 'objects',
)
);
This query retrieves up to 100 orders created before the beginning of the current year. The limit argument controls how many orders are retrieved in one batch.
Using batches is preferable to loading thousands of orders into memory at once.
3. Delete WooCommerce Orders Programmatically
Once you have retrieved the matching orders, you can delete them using the order object’s delete() method.
Pass true to permanently delete an order instead of moving it to the trash:
$order = wc_get_order( $order_id );
if ( $order ) {
$order->delete( true );
}
For a safer implementation, you should check the order date and process a limited number of orders at a time.
4. Complete Example: Delete Old WooCommerce Orders in Batches
The following example adds a deletion button to the WordPress dashboard. An administrator can click the button to permanently delete up to 100 orders created before the current year.
Each click processes one batch. If more matching orders remain, click the button again until the process reports that no old orders were found.
Add the code to a custom plugin or your active child theme’s functions.php file:
/**
* Display an admin button for deleting old WooCommerce orders.
*/
function geekerhub_old_orders_admin_notice() {
if ( ! current_user_can( 'manage_woocommerce' ) ) {
return;
}
if ( ! class_exists( 'WooCommerce' ) ) {
return;
}
$action_url = admin_url( 'admin-post.php' );
$nonce = wp_create_nonce(
'geekerhub_delete_old_orders'
);
echo '<div class="notice notice-warning">';
echo '<p>';
echo '<strong>Old WooCommerce Orders</strong>';
echo '</p>';
echo '<p>';
echo 'Permanently delete up to 100 orders created before ';
echo esc_html( wp_date( 'January 1, Y' ) );
echo '.';
echo '</p>';
echo '<form method="post" action="' . esc_url( $action_url ) . '">';
echo '<input type="hidden" name="action" value="geekerhub_delete_old_orders">';
echo '<input type="hidden" name="_wpnonce" value="' . esc_attr( $nonce ) . '">';
echo '<button type="submit" class="button button-secondary" onclick="return confirm(\'Permanently delete up to 100 old orders? Make sure you have a backup.\');">';
echo 'Delete Next 100 Old Orders';
echo '</button>';
echo '</form>';
echo '</div>';
}
add_action(
'admin_notices',
'geekerhub_old_orders_admin_notice'
);
/**
* Process one batch of old WooCommerce orders.
*/
function geekerhub_delete_old_orders_callback() {
// Check administrator permission.
if ( ! current_user_can( 'manage_woocommerce' ) ) {
wp_die( 'You are not allowed to delete orders.' );
}
// Verify the security nonce.
check_admin_referer( 'geekerhub_delete_old_orders' );
if ( ! function_exists( 'wc_get_orders' ) ) {
wp_die( 'WooCommerce is not available.' );
}
// Get the beginning of the current year.
$start_of_year = wp_date( 'Y-01-01' );
// Retrieve up to 100 old orders.
$orders = wc_get_orders(
array(
'limit' => 100,
'date_created' => '<' . strtotime( $start_of_year ),
'status' => array_keys( wc_get_order_statuses() ),
'return' => 'objects',
)
);
$deleted_count = 0;
if ( ! empty( $orders ) ) {
foreach ( $orders as $order ) {
// Permanently delete the order.
$result = $order->delete( true );
if ( $result ) {
$deleted_count++;
}
}
}
// Return to the previous admin page with the result.
$redirect_url = add_query_arg(
array(
'geekerhub_deleted' => $deleted_count,
),
admin_url()
);
wp_safe_redirect( $redirect_url );
exit;
}
add_action(
'admin_post_geekerhub_delete_old_orders',
'geekerhub_delete_old_orders_callback'
);
/**
* Display the result after processing a batch.
*/
function geekerhub_old_orders_delete_result() {
if ( ! current_user_can( 'manage_woocommerce' ) ) {
return;
}
if ( ! isset( $_GET['geekerhub_deleted'] ) ) {
return;
}
$deleted_count = absint(
wp_unslash( $_GET['geekerhub_deleted'] )
);
echo '<div class="notice notice-success is-dismissible">';
echo '<p>';
echo esc_html(
sprintf(
'Successfully deleted %d old order(s) in this batch.',
$deleted_count
)
);
echo '</p>';
echo '</div>';
}
add_action(
'admin_notices',
'geekerhub_old_orders_delete_result'
);
After adding the code, open your WordPress dashboard. You will see a notice with the Delete Next 100 Old Orders button.
Click the button to process a batch. Repeat the process until the result reports that zero old orders were deleted. At that point, no matching orders remain.
5. Why Use WooCommerce Functions Instead of Direct SQL Queries?
Older WooCommerce implementations often stored orders in the WordPress posts and postmeta tables. Developers sometimes used direct SQL queries to delete order records and their related metadata.
However, WooCommerce now supports High-Performance Order Storage (HPOS), which can store order data in dedicated WooCommerce tables instead of the traditional WordPress posts tables.
Directly deleting records from tables such as wp_posts, wp_postmeta, or wp_woocommerce_order_items can leave related data behind or fail to remove orders stored using HPOS.
Using wc_get_orders() and the order object’s delete() method allows WooCommerce to handle the deletion through its order data system.
You can learn more about the supported order retrieval methods in the official WooCommerce wc_get_orders() documentation and the WC_Order class reference.
Important Notes
- Permanent deletion: The
delete( true )method permanently deletes the order. It does not move it to the trash. - Batch processing: The example deletes up to 100 orders per request to reduce memory usage and avoid long-running requests.
- Current year: The date condition is generated dynamically using the WordPress site timezone.
- Order statuses: The query includes all registered WooCommerce order statuses, rather than only completed or processing orders.
- Permissions: Only users with the
manage_woocommercecapability can run the deletion action. - Security: The action uses a WordPress nonce to help protect against unauthorized requests.
- Custom order types: If your store uses additional custom order types or custom storage integrations, review their deletion requirements separately.
- Backups: Keep a verified database backup before running any bulk deletion process.
Conclusion
You can programmatically delete old WooCommerce orders without manually selecting them in the WordPress dashboard. WooCommerce provides order retrieval and deletion methods that make this process easier to manage.
The example in this tutorial retrieves orders created before the current year and permanently deletes them in batches of 100. It also provides an administrator-only button so you can control when each batch is processed.
Always test the code on a staging website and keep a complete backup before deleting customer order records from a live store.