WooCommerce – Duplicating Orders With HPOS
Many WooCommerce website owners also deal with Purchase Orders and generating new orders manually in the WooCommerce ‘Orders’ area. But, if you have generated previous orders, the billing and shipping data is not necessarily saved as a “user” account, so trying to generate a new order for customers that have never been created as a ‘customer’ user can be time consuming.
What if there was a simple way to duplicate an order from the same customer, which would recreate the billing and shipping address, and then being able to simply edit the products and quantity being ordered?
With the legacy WooCommerce tables, Rodolfo Melogli, a very helpful WooCommerce developer, had freely provided some code that could be placed in the functions.php file. This code creates a “Duplicate” button on the WooCommerce “Orders” page under the “Actions” column, and upon clicking the button, would quickly duplicate that order.
Rodolfo had also attempted to create code for WooCommerce HPOS (High Performance Order Storage) tables, however, we discovered it did not work. On his page, he admits the code was untested as he himself does not yet use WooCommerce HPOS tables (although he recently migrated to them on at least one site and delivered a very detailed presentation about how he did it on a live site).
Having at least one client that now makes use of WooCommerce HPOS tables, and who also receives a significant number of Purchase Orders from their clients, we were eager to help them easily duplicate existing orders within WooCommerce.
Naturally, we first tried Rodolfo’s snippet for HPOS tables, but it simply did not work. It did create the “Duplicate” button, but when clicking it, the order was not duplicated and there was no error message.
We were using a staging/development site for testing and we went through a lot of trial and error while also studying the HPOS tables. To develop the snippet, we had all plugins deactivated except for WooCommerce and at a variety of steps, had debugging turned on to see what was going on. With a bit of help from ChatGPT, we learned that the new HPOS tables uses
wc_create_order()
to create orders. So, this meant that the code snippet that Rodolfo had published needed some changes. Here is the full snippet that was tested and worked with WooCommerce V.9.6.2 with HPOS Tables:
/** * @snippet Duplicate WooCommerce Order (HPOS-Compatible) * @how-to ianscottgroup.com/woocommerce-duplicating-orders-with-hpos/ * @author Rodolfo Melogli, Ian Scott, (And A Little Help From ChatGPT) * @compatible WooCommerce 9.6.2 with HPOS enabled */ add_filter( 'woocommerce_admin_order_actions', 'hpos_duplicate_order_button', 9999, 2 ); function hpos_duplicate_order_button( $actions, $order ) { // Note: Removed "post_type=shop_order" from the URL. $actions['duplicate_order'] = array( 'url' => wp_nonce_url( admin_url( 'edit.php?action=duplicate_order&order_id=' . $order->get_id() ), 'hpos-duplicate-order' ), 'name' => __( 'Duplicate', 'woocommerce' ), 'action' => 'duplicate_order', ); return $actions; } add_action( 'admin_head', 'hpos_duplicate_order_button_css' ); function hpos_duplicate_order_button_css() { echo ''; } add_action( 'admin_action_duplicate_order', 'hpos_duplicate_order' ); function hpos_duplicate_order() { // Verify permissions and nonce. if ( ! current_user_can( 'edit_shop_orders' ) || ! isset( $_GET['order_id'] ) || ! check_admin_referer( 'hpos-duplicate-order' ) ) { wp_die( __( 'You do not have permission to duplicate this order.', 'woocommerce' ) ); } $order_id = absint( wp_unslash( $_GET['order_id'] ) ); $original_order = wc_get_order( $order_id ); if ( ! $original_order ) { wp_die( __( 'Invalid order.', 'woocommerce' ) ); } // Create a new order using the WooCommerce API. $new_order = wc_create_order( [ 'status' => 'pending', 'created_via' => 'order-duplication' ] ); if ( ! $new_order ) { wp_die( __( 'Order duplication failed.', 'woocommerce' ) ); } // Copy basic order details. $new_order->set_customer_id( $original_order->get_customer_id() ); $new_order->set_address( $original_order->get_address( 'billing' ), 'billing' ); $new_order->set_address( $original_order->get_address( 'shipping' ), 'shipping' ); $new_order->set_currency( $original_order->get_currency() ); $new_order->set_payment_method( $original_order->get_payment_method() ); $new_order->set_payment_method_title( $original_order->get_payment_method_title() ); // Copy order meta data. foreach ( $original_order->get_meta_data() as $meta ) { $new_order->update_meta_data( $meta->key, $meta->value ); } // Duplicate line items. foreach ( $original_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_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 ( $original_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 methods. foreach ( $original_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 ( $original_order->get_taxes() as $tax ) { $new_tax = new WC_Order_Item_Tax(); $new_tax->set_rate_id( $tax->get_rate_id() ); $new_tax->set_total( $tax->get_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; }
How To Use The Above HPOS Duplicate Orders Snippet
The above snippet requires that your WooCommerce setup is using HPOS Tables. If you are using the Legacy Tables, Rodolfo’s snippet on this page will work (we tested that as well with the legacy tables).
Copy the above code and paste into your theme’s (hopefully you are using a child theme) functions.php file. Save the file.
Navigate to your WooCommerce “Orders” page. Under “Screen Options” (at the top of the page, ensure you have ticked the “Actions” checkbox.
Scroll down to your orders. In the “Actions” Column, you should see a new “Duplicate Button”:
In this WooCommerce setup, the Duplicate Button is the one in the middle, with the “+” sign. Click that button for whatever order you want to duplicate, and you should have a new clone of that order, that you can now edit!
As of February 28, 2025, this snippet works with WooCommerce V. 9.6.2.