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, 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
Note: this is untested as I don’t use 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( '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->prefix}wc_orders WHERE id = %d", $order_id ) );
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->prefix}wc_orders", $order );
$new_order_id = $wpdb->insert_id;
$meta = $wpdb->get_results( $wpdb->prepare( "SELECT meta_key, meta_value FROM {$wpdb->prefix}wc_order_metadata 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_order_metadata", [
'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}wc_order_items WHERE order_id = %d", $order_id ) );
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}wc_order_items", $item );
$new_item_id = $wpdb->insert_id;
$item_meta = $wpdb->get_results( $wpdb->prepare( "SELECT meta_key, meta_value FROM {$wpdb->prefix}wc_order_itemmeta WHERE order_item_id = %d", $old_item_id ) );
foreach ( $item_meta as $meta ) {
$wpdb->insert( "{$wpdb->prefix}wc_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;
}
}