How to Sync Variations into Simple Products Between Stores

Recently, a client contacted me about my Simple WP Crossposting plugin, and he asked me whether it is possible to not only sync variable products between WooCommerce stores, but also to automatically convert all variations into simple products when syncing to a specific store.

“Of course, it is possible!” – I said.

In this guide, I am going to share with you how I achieved that, and what are the moments you need to keep in mind. I will also show you how to do that for a WordPress Multisite network, i.e., when using my other plugin – Simple Multisite Crossposting.

Comparing Simple Product Data vs Variation Data

Before diving into the actual solutions, let’s do some exploration first. I mean, when you convert a variation into a simple product, you need to take into consideration that variations in WooCommerce do not have as much data as products do. Besides, the same data of the WC_Product_Variation object may serve a different purpose than in WC_Product_Simple.

To visualize all the data entirely, I created a simple table for you.

By the way, I used WooCommerce REST API product properties as values in the first column.

Product dataSimple productsVariations
nameYesYes
slugYesNo
descriptionYesYes
short_descriptionYesNo
statusYesPartly
typeYesNo
sold_individuallyYesParent
purchase_noteYesNo
menu_orderYesPartly
reviews_allowedYesParent
catalog_visibilityYesParent
featuredYesParent
date_created_gmtYesParent
date_modified_gmtYesParent
regular_priceYesYes
sale_priceYesYes
date_on_sale_fromYesYes
date_on_sale_toYesYes
skuYesYes
global_unique_idYesYes
manage_stockYesYes
stock_quantityYesYes
backordersYesYes
low_stock_amountYesYes
stock_statusYesYes
is_virtualYesYes
weightYesYes
dimensionsYesYes
shipping_classYesYes
downloadableYesYes
download_limitYesYes
download_expiryYesYes
downloadsYesYes
imagesYesNo
imageNoYes
attributesYesPartly
upsell_idsYesNo
cross_sell_idsYesNo
meta_dataYesYes (but rarely used)

Now, let’s take a look at those table columns that I marked as “Partly”. What does it mean? I will describe it to you for each property separately:

  • status – Here, for simple products, we have all the normal post statuses available, like publish, pending, trash, private, etc. But for variations, we have only two statuses, which represent the behavior of the “Enabled” checkbox – if the checkbox is checked, the status is publish, if unchecked – private.
  • menu_order – The idea here is that though variations also have this property in their objects, the thing is that they represent a variation position within the product related to other variations. It means that we can not transfer this parameter to converted simple products after all.
  • attributes – For variations, attributes are stored in a more simplified way and have a unique value for each attribute.

That’s pretty much it. Now we’re ready to start syncing products between our stores and convert variations into products.

Convert Variations into Simple Products When Syncing Between Standalone WooCommerce Stores

Let’s start with standalone stores connected with the WooCommerce REST API. If you’re interested in a solution for WordPress multisite networks, you can find it below as well.

Programmatic approach

If you were looking for a solution on how to implement it programmatically, then here it is. Below is just raw code; of course, you need to use it as a part of a custom function, or probably connect to a save_post_product hook or something like that.

Needless to say, the code needs to be used on the initial store (not on the target one).

// it is a variable product ID
$parent_product_id = 12345;
$parent_product = wc_get_product( $parent_product_id );

// it is time to get its variations
$variation_ids = $parent_product->get_children();
if( $variation_ids ) {

	// when sending a WooCommerce REST API request we will use batches
	$data = array(
		'create' => array(),
		// sometimes we need to update existing products, in that case, you will need my Simple WP Crossposting plugin
		// 'update' => array(), 
	);

	foreach( $variation_ids as $variation_id ) {
		
		$variation = wc_get_product( $variation_id );

		// collect data for a simple product
		$product_data = array(
			'name' => $variation->get_name(),
			'description' => $variation->get_description(),
			'type' => 'simple',
			'images' => array(),
			'status' => $variation->get_status(),
			'meta_data' => array(),
			// prices
			'regular_price' => $variation->get_regular_price(),
			'sale_price' => $variation->get_sale_price(),
			'date_on_sale_from' => $variation->get_date_on_sale_from(),
			'date_on_sale_to' => $variation->get_date_on_sale_to(),
			// stock information
			'sku' => get_post_meta( $variation_id, '_sku', true ),
			'global_unique_id' => get_post_meta( $variation_id, '_global_unique_id', true ),
			'manage_stock' => $variation->get_manage_stock(),
			'stock_quantity' => (int) $variation->get_stock_quantity(),
			'backorders' => $variation->get_backorders(),
			'low_stock_amount' => ( $variation->get_low_stock_amount() ? (int) $variation->get_low_stock_amount() : null ),
			'stock_status' => $variation->get_stock_status(),
			'weight' => $variation->get_weight(),
			'dimensions' => $variation->get_dimensions(),
			'shipping_class' => $variation->get_shipping_class(),
			// parent product data
			'sold_individually' => $parent_product->get_sold_individually(),
			'reviews_allowed' => $parent_product->get_reviews_allowed(),
			'catalog_visibility' => $parent_product->get_catalog_visibility(),
			'featured' => $parent_product->get_featured(),
		);
		
		// image
		if( $variation->get_image_id() ) {
			$image_url = wp_get_attachment_url( $variation->get_image_id() );
			$product_data[ 'images' ][] = array(
				'src' => $image_url,
			);
		}
		
		// meta data
		foreach( $variation->get_meta_data() as $meta ) {
			$product_data[ 'meta_data' ][] = array(
				'key' => $meta->key,
				'value' => $meta->value,
			);
		}

		$data[ 'create' ][] = $product_data;

	}
	
	$woocommerce->post( 'products/batch', $data );

}	

Some important key points from the code above:

  • The first and most important thing to keep in mind is that a variable product can have quite a number of variations; it could even be a hundred variations. In our case, it means that we must use /products/batch WooCommerce REST API endpoint, but never /products, because imagine a situation when your custom function will try to send 100+ REST API requests at the same time. Believe me, you won’t like it.
  • To get a variation’s sku and global_unique_id, I am also using the get_post_meta() function, because if a variation doesn’t have one, when you try to get it from the object, it will try to collect it from the parent product, which we don’t need here.
  • More about the $woocommerce variable and connecting to the WooCommerce REST API, you can read in my other article.

The code is heavily simplified, so you might also want the following functionality:

  • Copying images added with ACF, for example, in variations custom fields.
  • When you sync variations into simple products from a store that is located on a localhost or on another inaccessible server, you might notice that images are not getting through, because, by default, WooCommerce REST API doesn’t support that, and you will need a different approach here.
  • Updating existing simple products that have already been synced before. As you can see, the array of data only has the $data[ 'create' ] subarray, but it doesn’t have the one that allows you to update existing products – $data[ 'update' ]. We need to additionally send a REST API request to check a product ID by the SKU that has already been synced, or simply store the information in the product meta.
  • Downloadable variations and downloadable products are also not implemented in my code.

If you’re interested in the functionality that I skipped in the code above, please continue reading to the plugin approach, where it is fully implemented.

Using a plugin – Simple WP Crossposting

I’ve been thinking about adding a checkbox to my Simple WP Crossposting plugin’s settings page, like in the screenshot below:

Convert variations into simple products when syncing between WooCommerce stores
This checkbox is still in my plans, but the functionality is ready as a separate add-on.

So, basically, when syncing a variable product to a target store, each product’s variation will be synced as a separate simple product and connected by SKU (in other words, if you have variations with “SKU-1”, “SKU-2” on “Store 1”, after crossposting, you will have simple products with the same SKUs on “Store 2”).

But I don’t want to overload the plugin with functionality unless I am 100% sure it is useful and keeps the plugin lightweight as it currently is. So, at the moment, I am still exploring the feedback, whether this feature is really needed or it is just a once-in-a-year request. Please let me know your thoughts in the comments.

For now, this feature is implemented as a separate add-on, which is available after a plugin purchase.

Simple WP Crossposting Variations to Simple Products add-on
The add-on doesn’t have any settings (yet) and works automatically when activated.

Convert Variations into Simple Products When Syncing Between Subsites Within WordPress Multisite

Ok, but what if your WooCommerce stores are a part of a multisite network? Is there any difference in how we can handle it?

To be honest, everything I described above will work great for multisite networks; however, it will not make any sense to go the WooCommerce REST API route, because it is not only resource-heavy, but also why do you need to establish a connection between sites via the REST API, when your sites are on the same server and share the same database?

That’s why I have different plugins:

Well, that was a little bit of theory; now let’s jump into the programmatic and non-programmatic approaches.

Programmatic approach

There is actually a whole tutorial about creating products programmatically; you can start with it before diving into the code below.

The code is better to use as a part of your custom function or connect to some hook in WooCommerce, like save_post_product or woocommerce_update_product.

// the hardcoded parameters, some of them we can get from the hook arguments as well
$target_blog_id = 2;
$parent_product_id = 12345;

// we still get a WC_Product object and obtain its children (variations)
$parent_product = wc_get_product( $parent_product_id );
// please note, that it is better to double-check that the product is variable
$variation_ids = $parent_product->get_children();

if( $variation_ids ){

	switch_to_blog( $target_blog_id );

	foreach( $variation_ids as $variation_id ) {

		$variation = wc_get_product( $variation_id );

		if( ! $variation ){
			continue;
		}
		
		// we are about to create a simple product
		$product = new WC_Product_Simple();

		$product->set_name( $variation->get_name() );
		$product->set_description( $variation->get_description() );
		$product->set_status( $variation->get_status() );
		// prices
		$product->set_regular_price( $variation->get_regular_price() );
		$product->set_sale_price( $variation->get_sale_price() );
		$product->set_date_on_sale_from( $variation->get_date_on_sale_from() );
		$product->set_date_on_sale_to( $variation->get_date_on_sale_to() );
		// inventory
		$product->set_sku( get_post_meta( $variation_id, '_sku', true ) );
		$product->set_global_unique_id( get_post_meta( $variation_id, '_global_unique_id', true ) );
		$product->set_manage_stock( $variation->get_manage_stock() );
		$product->set_stock_quantity( $variation->get_stock_quantity() );
		$product->set_backorders( $variation->get_backorders() );
		$product->set_stock_status( $variation->get_stock_status() );
		$product->set_low_stock_amount( $variation->get_low_stock_amount() );
		$product->set_weight( $variation->get_weight() );
		$product->set_length( $variation->get_length() );
		$product->set_width( $variation->get_width() );
		$product->set_height( $variation->get_height() );
		$product->set_shipping_class_id( $variation->get_shipping_class_id() );
		// from its parent product
		$product->set_sold_individually( $parent_product->get_sold_individually() );
		$product->set_reviews_allowed( $parent_product->get_reviews_allowed() );
		$product->set_catalog_visibility( $parent_product->get_catalog_visibility() );
		$product->set_featured( $parent_product->get_featured() );
		
		foreach( $variation->get_meta_data() as $meta ) {
			$product->add_meta_data( $meta->key, $meta->value );
		}

		$product->save();
		
	}

	restore_current_blog();
}

The code is simplified for the sake of the tutorial; if you’re going to develop a functionality based on it, don’t forget about images and downloads.

Speaking of images, when you sync products between sub-sites in a multisite network (or, in our case, variations into products), you will need to programmatically copy images to the target store as well, unless you’re using the Multisite Shared Media Library plugin, of course. If you’re interested in how to do it, please feel free to ask me for hints in the comments below, or you can just use a plugin approach.

Using a plugin – Simple Multisite Crossposting

The idea with the plugin is the same as for the standalone stores. At first, I was thinking about adding a checkbox into the plugin settings, kind of like this one:

WooCommerce Multisite convert variations into simple products

But since I’m not quite sure how reasonable it would be to include this functionality in the core version of the plugin, it is available as an extra add-on for Simple Multisite Crossposting.

Have any questions? Feel free to ask in the comments below.

Misha Rudrastyh

Misha Rudrastyh

Hey guys and welcome to my website. For more than 15 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