WooCommerce: Duplicate Order @ WordPress Dashboard

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.

Here are the duplicate buttons you’ll see once the snippet below is active on your website.

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_countspost 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;
}

Where to add custom code?

You should place custom PHP in functions.php and custom CSS in style.css of your child theme: where to place WooCommerce customization?

This code still works, unless you report otherwise. To exclude conflicts, temporarily switch to the Storefront theme, disable all plugins except WooCommerce, and test the snippet again: WooCommerce troubleshooting 101

Related content

Rodolfo Melogli

Business Bloomer Founder

Author, WooCommerce expert and WordCamp speaker, Rodolfo has worked as an independent WooCommerce freelancer since 2011. His goal is to help entrepreneurs and developers overcome their WooCommerce nightmares. Rodolfo loves travelling, chasing tennis & soccer balls and, of course, wood fired oven pizza. Follow @rmelogli

4 thoughts on “WooCommerce: Duplicate Order @ WordPress Dashboard

  1. 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.

    1. I’ve now fixed it, try the new version please. Thank you!

      1. Hi Rodolfo, I’ve just tested the HPOS version of the code and works fine. I’ve duplicated some orders easily πŸ˜‰

Questions? Feedback? Customization? Leave your comment now!
_____

If you are writing code, please wrap it like so: [php]code_here[/php]. Failure to complying with this, as well as going off topic or not using the English language will result in comment disapproval. You should expect a reply in about 2 weeks - this is a popular blog but I need to get paid work done first. Please consider joining the Business Bloomer Club to get quick WooCommerce support. Thank you!

Your email address will not be published. Required fields are marked *