In WooCommerce, the ability to quickly duplicate orders can save time when managing repeat purchases or creating similar orders. However, this feature isnβt available by default. While plugins exist to add it, they can be complex or overengineered for simple use cases.
This custom PHP snippet adds a “Duplicate” button to the order actions in the WordPress dashboard. It duplicates the order directly in the database wherever possible, offering a lightweight solution without unnecessary overhead.
If you need a practical and efficient way to duplicate WooCommerce orders directly from the “Orders” admin page, this approach offers a tailored solution that integrates seamlessly with your existing workflow.

PHP Snippet: Add “Duplicate Order” Button @ WooCommerce Orders Table Action
Notes:
- This snippet will set the cloned order status to “pending”.
- This won’t reduce the stock on the duplicate order, if the original order reduced stock. This is because the original order has the ‘_order_stock_reduced‘ post meta set to “yes”. The good thing, though, is that you can skip the duplication of this specific post meta by using the ‘bbloomer_excluded_order_meta_keys‘ filter which is provided below, and therefore expect a stock reduction whenever you set the order status to one of the stock reducing statuses.
- This won’t assign downloadable permissions in case the duplicate order has downloadable products and you set the order status to completed, for the exact reasons as ‘_order_stock_reduced‘ post meta. In this case, you need to exclude ‘download_permissions_granted‘ via the ‘bbloomer_excluded_order_meta_keys‘ filter, so you can grant them when you complete the order
- Probably, the same problem happens with ‘new_order_email_sent‘, ‘recorded_sales‘, ‘recorded_coupon_usage_counts‘ post meta
- I don’t use WooCommerce Analytics so not sure if the duplicated order is synced once you complete it
Duplicate Order Snippet #1 – No HPOS
/**
* @snippet Duplicate WooCommerce Order (No HPOS)
* @how-to businessbloomer.com/woocommerce-customization
* @author Rodolfo Melogli, Business Bloomer
* @compatible WooCommerce 9
* @community https://businessbloomer.com/club/
*/
add_filter( 'woocommerce_admin_order_actions', 'bbloomer_duplicate_order_button', 9999, 2 );
function bbloomer_duplicate_order_button( $actions, $order ) {
$actions['duplicate_order'] = array(
'url' => wp_nonce_url( admin_url( 'edit.php?post_type=shop_order&action=duplicate_order&order_id=' . $order->get_id() ), 'bbloomer-duplicate-order' ),
'name' => __( 'Duplicate', 'woocommerce' ),
'action' => 'duplicate_order',
);
return $actions;
}
add_action( 'admin_head', 'bbloomer_duplicate_order_button_css' );
function bbloomer_duplicate_order_button_css() {
echo '<style>.widefat .column-wc_actions a.wc-action-button-duplicate_order::after { font-family: WooCommerce; content: "\e007" }</style>';
}
add_action( 'admin_action_duplicate_order', 'bbloomer_duplicate_order' );
function bbloomer_duplicate_order() {
if ( current_user_can( 'edit_shop_orders' ) && isset( $_GET['order_id'] ) && check_admin_referer( 'bbloomer-duplicate-order' ) ) {
global $wpdb;
$order_id = absint( wp_unslash( $_GET['order_id'] ) );
$order = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->posts} WHERE ID = %d", $order_id ), ARRAY_A );
if ( ! $order ) {
return new WP_Error( 'invalid_order', __( 'Order not found' ) );
}
unset( $order['ID'] );
$order['post_date'] = current_time( 'mysql' );
$order['post_date_gmt'] = current_time( 'mysql', 1 );
$order['post_status'] = apply_filters( 'bbloomer_duplicate_order_status', 'wc-pending', $order_id );
$wpdb->insert( $wpdb->posts, $order );
$new_order_id = $wpdb->insert_id;
$meta = $wpdb->get_results( $wpdb->prepare( "SELECT meta_key, meta_value FROM {$wpdb->postmeta} WHERE post_id = %d", $order_id ) );
$excluded_meta_keys = apply_filters( 'bbloomer_excluded_order_meta_keys', [], $order_id );
foreach ( $meta as $meta_row ) {
if ( in_array( $meta_row->meta_key, $excluded_meta_keys, true ) ) {
continue;
}
$wpdb->insert( $wpdb->postmeta, [
'post_id' => $new_order_id,
'meta_key' => $meta_row->meta_key,
'meta_value' => $meta_row->meta_value
] );
}
$items = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}woocommerce_order_items WHERE order_id = %d", $order_id ), ARRAY_A );
foreach ( $items as $item ) {
$old_item_id = $item['order_item_id'];
unset( $item['order_item_id'] );
$item['order_id'] = $new_order_id;
$wpdb->insert( "{$wpdb->prefix}woocommerce_order_items", $item );
$new_item_id = $wpdb->insert_id;
$item_meta = $wpdb->get_results( $wpdb->prepare( "SELECT meta_key, meta_value FROM {$wpdb->prefix}woocommerce_order_itemmeta WHERE order_item_id = %d", $old_item_id ) );
foreach ( $item_meta as $meta ) {
$wpdb->insert( "{$wpdb->prefix}woocommerce_order_itemmeta", [
'order_item_id' => $new_item_id,
'meta_key' => $meta->meta_key,
'meta_value' => $meta->meta_value
] );
}
}
wp_safe_redirect( admin_url( 'edit.php?post_type=shop_order' ) );
exit;
}
}
Duplicate Order Snippet #2 – HPOS
/**
* @snippet Duplicate WooCommerce Order (HPOS)
* @how-to businessbloomer.com/woocommerce-customization
* @author Rodolfo Melogli, Business Bloomer
* @compatible WooCommerce 9
* @community https://businessbloomer.com/club/
*/
add_filter( 'woocommerce_admin_order_actions', 'bbloomer_duplicate_order_button', 9999, 2 );
function bbloomer_duplicate_order_button( $actions, $order ) {
$actions['duplicate_order'] = array(
'url' => wp_nonce_url( admin_url( 'admin-ajax.php?action=duplicate_order&order_id=' . $order->get_id() ), 'bbloomer-duplicate-order' ),
'name' => __( 'Duplicate', 'woocommerce' ),
'action' => 'duplicate_order',
);
return $actions;
}
add_action( 'admin_head', 'bbloomer_duplicate_order_button_css' );
function bbloomer_duplicate_order_button_css() {
echo '<style>.widefat .column-wc_actions a.wc-action-button-duplicate_order::after { font-family: WooCommerce; content: "\e007" }</style>';
}
add_action( 'wp_ajax_duplicate_order', 'bbloomer_duplicate_order' );
function bbloomer_duplicate_order() {
if ( ! current_user_can( 'edit_shop_orders' ) ) return;
if ( ! isset( $_GET['order_id'] ) ) return;
if ( ! check_admin_referer( 'bbloomer-duplicate-order' ) ) return;
global $wpdb;
$order_id = absint( wp_unslash( $_GET['order_id'] ) );
$order = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}wc_orders WHERE id = %d", $order_id ), ARRAY_A );
if ( ! $order ) {
return new WP_Error( 'invalid_order', __( 'Order not found' ) );
}
$new_order = wc_create_order();
$order['id'] = $new_order->get_id();
$order['date_created_gmt'] = current_time( 'mysql', 1 );
$order['date_updated_gmt'] = current_time( 'mysql', 1 );
$order['status'] = apply_filters( 'bbloomer_duplicate_order_status', 'wc-pending', $order_id );
$wpdb->update( "{$wpdb->prefix}wc_orders", $order, [ 'id' => $order['id'] ] );
$new_order_id = $order['id'];
if ( ! $new_order_id ) {
return;
}
$order = wc_get_order( $order_id );
// Duplicate addresses
$new_order->set_address( $order->get_address( 'billing' ), 'billing' );
$new_order->set_address( $order->get_address( 'shipping' ), 'shipping' );
// Duplicate order meta
$meta = $wpdb->get_results( $wpdb->prepare( "SELECT meta_key, meta_value FROM {$wpdb->prefix}wc_orders_meta WHERE post_id = %d", $order_id ) );
$excluded_meta_keys = apply_filters( 'bbloomer_excluded_order_meta_keys', [], $order_id );
foreach ( $meta as $meta_row ) {
if ( in_array( $meta_row->meta_key, $excluded_meta_keys, true ) ) {
continue;
}
$wpdb->insert( "{$wpdb->prefix}wc_orders_meta", [
'order_id' => $new_order_id,
'meta_key' => $meta_row->meta_key,
'meta_value' => $meta_row->meta_value
] );
}
// Duplicate products
foreach ( $order->get_items() as $item ) {
if ( 'line_item' === $item->get_type() ) {
$new_item = new WC_Order_Item_Product();
$new_item->set_product_id( $item->get_product_id() );
$new_item->set_variation_id( $item->get_variation_id() );
$new_item->set_name( $item->get_name() );
$new_item->set_quantity( $item->get_quantity() );
$new_item->set_subtotal( $item->get_subtotal() );
$new_item->set_total( $item->get_total() );
foreach ( $item->get_meta_data() as $meta ) {
$new_item->add_meta_data( $meta->key, $meta->value, true );
}
$new_order->add_item( $new_item );
}
}
// Duplicate fees
foreach ( $order->get_fees() as $fee ) {
$new_fee = new WC_Order_Item_Fee();
$new_fee->set_name( $fee->get_name() );
$new_fee->set_total( $fee->get_total() );
$new_fee->set_tax_class( $fee->get_tax_class() );
$new_fee->set_taxes( $fee->get_taxes() );
$new_order->add_item( $new_fee );
}
// Duplicate shipping
foreach ( $order->get_shipping_methods() as $shipping ) {
$new_shipping = new WC_Order_Item_Shipping();
$new_shipping->set_method_title( $shipping->get_method_title() );
$new_shipping->set_total( $shipping->get_total() );
$new_shipping->set_taxes( $shipping->get_taxes() );
$new_order->add_item( $new_shipping );
}
// Duplicate taxes
foreach ( $order->get_taxes() as $tax ) {
$new_tax = new WC_Order_Item_Tax();
$new_tax->set_rate_id( $tax->get_rate_id() );
$new_tax->set_tax_total( $tax->get_tax_total() );
$new_tax->set_shipping_tax_total( $tax->get_shipping_tax_total() );
$new_order->add_item( $new_tax );
}
// Finalize the new order
$new_order->calculate_totals();
$new_order->save();
wp_safe_redirect( admin_url( 'edit.php?post_type=shop_order' ) );
exit;
}
I can confirm that the HPOS version does not appear to work. Using WooCommerce V. 9.6.2 and on a site recently migrated to HPOS. The site was built on the Beaver Theme.
The code does make the “Duplicate Order” button on the Orders page, however, when clicking it, the Orders page reloads but there is no new duplicated order. In addition, there are the same number of orders showing as before clicking the button.
I tried this on a previously canceled order as well as a newly created order and a Completed order.
On an almost perfectly cloned site that is still using Legacy WooCommerce tables (No-HPOS), the first snippet (No-HOPS) works brilliantly.
I’ve now fixed it, try the new version please. Thank you!
Hi Rodolfo, I’ve just tested the HPOS version of the code and works fine. I’ve duplicated some orders easily π
Yay!