Bulk Copy and Update Products between WooCommerce Stores

Just another tutorial by your requests guys.

In this one we are about to create a bulk action like on the screenshot below which allows to sync products between two (or multiple) WooCommerce stores.

WooCommerce Copy Products and Products data from one site to another
If a product has already been published on Store 2, then we just update its product data.

Before we dive into the coding part, I would like to remind you that there is an add-on which is a part of my Simple WP Crossposting plugin that does exactly the same.

1. Add a New Dropdown Option into Bulk Actions

Let’s begin with adding just a bulk action option into the dropdown, once again, you can find a little bit more information about it in my bulk actions tutorial.

add_filter( 'bulk_actions-edit-product', 'rudr_product_bulk_action' );

function rudr_product_bulk_action( $bulk_actions ) {
	$bulk_actions[ 'rudr_crosspost_product' ] = 'Publish/Update on Store 2';
	return $bulk_actions;

I am sure some of you are wondering – what if you have multiple WooCommerce stores? Not only “Store2”? The long story short, you can just duplicate line 5 miltiple times (for every store) or just use my add-on for that.

create a bulk action for WooCommerce products

That was a simple part and now we are ready for an interesting part 🙂

2. Copy Products or Update Product Data from One Site to Another

Before we begin a couple moments to keep in mind:

add_filter( 'handle_bulk_actions-edit-product', 'rudr_copy_products' );

function rudr_copy_products( $redirect, $doaction, $object_ids ) {
	// remove our query args if any
	$redirect = remove_query_arg( array( 'so_many_products', 'products_done', 'products_err' ), $redirect );
	// do nothing if it is not our bulk action
	if( 'rudr_crosspost_product' !== $doaction ) {
		return $redirect;
	// in case more than 100 products selected
	if( count( $object_ids ) >= 100 ) {
		return add_query_arg( 'so_many_products', $object_ids, $redirect );
	// now our goal to gather all products data into one array
	$products_data = array(
		'create' => array(),
		'update' => array(),
	foreach( $object_ids as $product_id ) {
		$product = wc_get_product( $product_id );
		// you can always double check
		if( ! $product ) {
		// get all product data in one go
		$product_data = $product->get_data();
		// we can process the data before sending in into the request
		// for example we can remove what we do not need
		unset( $product_data[ 'id' ] );
		unset( $product_data[ 'date_created' ] );
		unset( $product_data[ 'date_modified' ] );
		// we can modify some data as well
		if( $product_data[ 'image_id' ] ) {
			$image_url = wp_get_attachment_image_url( $product_data[ 'image_id' ] );
			unset( $product_data[ 'image_id' ] );
			$product_data[ 'images' ] = array(
					'src' => $image_url, 
		// (the same should be done for $product_data[ 'gallery_image_ids' ] )
		if( $id = some_function_to_check_if_already_published() ) {
			$product_data[ 'id' ] = $id;
			$products_data[ 'update' ][] = $product_data;
		} else {
			$products_data[ 'create' ][] = $product_data;
	// all is ready, let's create a request
	$request = wp_remote_post(
			'headers' => array(
				'Authorization' => 'Basic ' . base64_encode( "{$username}:{$application_password}" )
			'body' => $products_data
	if( 'OK' === wp_remote_retrieve_response_message( $request ) ) {
		return add_query_arg( 'products_done', $object_ids, $redirect );
	} else {
		return add_query_arg( 'products_err', $object_ids, $redirect );

I believe more clarifications are needed here:

3. Create Admin Notices

The last but not the least – it is quite obvious that we have to display a success message when products are successully published or updated on Store 2 or an error message if something is not right.

For example like this:

WooCommerce REST API batch requests are limited to 100 products per request

And the code:

add_action( 'admin_notices', function() {
	if( ! empty( $_REQUEST[ 'so_many_products' ] ) ) {

			'<div id="message" class="error notice is-dismissible"><p>You have selected %s products. Please select less than 100.</p></div>',
			absint( $_REQUEST[ 'so_many_products' ] )


	if( ! empty( $_REQUEST[ 'products_done' ] ) ) {

		printf( '<div id="message" class="updated notice is-dismissible"><p>' .
			_n( '%s product has been successfully published/updated.', '%s products have been successfully published/updated.', absint( $_REQUEST[ 'products_done' ] ) )
			. '</p></div>', absint( $_REQUEST[ 'products_done' ] ) );


	if( ! empty( $_REQUEST[ 'products_err' ] ) ) {
		echo '<div id="message" class="error notice is-dismissible"><p>Something went wrong.</p></div>';
} );
Misha Rudrastyh

Misha Rudrastyh

Hey guys and welcome to my website. For more than 10 years I've been doing my best to share with you some superb WordPress guides and tips for free.

Need some developer help? Contact me

Follow me on X