WooCommerce API – Product Sync with Multiple Stores
First of all we have to decide what is actually a product synchronisation? Let’s consider two scenarios here.
- Update a product with the same SKU on other WooCommerce stores automatically when it was updated by an administrator on the “Main store”. Here you can decide whether you would like to update a specific product data or all product data. And it is what we are going to do in this tutorial with code or with a plugin.
- Update product stock status and quantity on other WooCommerce stores not only when this product was updated by an administrator but also when the product was purchased (and its quantity was descreased, obviously). We are going to talk a little bit about it in this tutorial as well, but I recommend you to take a look at my another plugin which is intended to help you with that.
Without a plugin – using save_post action hook
Prepare WooCommerce stores authentication data
I assume that you would like to sync your products with multiple stores, so for each of the them we have to prepare a set of data:
- Store URL,
- Login and Application Password.
I am going to use an array for that.
$stores = array(
// store 1
array(
'url' => '', // store URL goes here with https://
'login' => '',
'pwd' => '',
),
// store 2
array(
'url' => '',
'login' => '',
'pwd' => '',
),
// store 3 etc
);
It is fully up to you how you would like to process and store this data, but will definitely use $stores
array in the next chapter of this tutorial.
Sync a product with other WooCommerce stores when it was updated
In this chapter we are going to create multiple WooCommerce REST API requests in order to sync (publish or update) a product when it was published or updated in admin.
class Misha_Sync_Woo_Products {
public function __construct() {
add_action( 'save_post_product', array( $this, 'sync' ), 99, 2 );
}
public function sync( $product_id, $post ) {
// do nothing if WooCommerce is not installed
if( ! function_exists( 'wc_get_product' ) ) {
return;
}
// get product object
$product = wc_get_product( $product_id );
// do the sync for published products only
if( 'publish' !== $product->get_status() ) {
return;
}
// that's an array of stores auth information we previously discussed
$stores = array( ... );
// prepare product data before the loop
$product_data = $product->get_data();
// Fix: "Error 400. Bad Request. Cannot create existing product."
unset( $product_data[ 'id' ] );
// Fix: "Error 400 Bad Request low_stock_amount is not of type integer,null." error
$product_data[ 'low_stock_amount' ] = (int) $product_data[ 'low_stock_amount' ];
// In order to sync a product image we have to do some additional stuff
if( $product_data[ 'image_id' ] ) {
$product_data[ 'images' ] = array(
array(
'src' => wp_get_attachment_url( $product_data[ 'image_id' ] )
)
);
unset( $product_data[ 'image_id' ] );
}
// let's loop through multiple stores and sync the product with each of them
foreach( $stores as $store ) {
$endpoint = "{$store[ 'url' ]}/wp-json/wc/v3/products";
$method = 'POST';
// let's check if the product with the same SKU already exists
if( $product_id_2 = $this->product_exists( $product, $store ) ) {
$endpoint = "{$endpoint}/{$product_id_2}";
$method = 'PUT';
}
// do the sync
wp_remote_request(
$endpoint,
array(
'method' => $method,
'headers' => array(
'Authorization' => 'Basic ' . base64_encode( "{$store[ 'login' ]}:{$store[ 'pwd' ]}" )
),
'body' => $product_data
)
);
}
}
private function product_exists( $product, $store ) {
$sku = $product->get_sku();
$request = wp_remote_get(
add_query_arg( 'sku', $sku, "{$store[ 'url' ]}/wp-json/wc/v3/products" ),
array(
'headers' => array(
'Authorization' => 'Basic ' . base64_encode( "{$store[ 'login' ]}:{$store[ 'pwd' ]}" )
)
)
);
if( 'OK' === wp_remote_retrieve_response_message( $request ) ) {
$products = json_decode( wp_remote_retrieve_body( $request ) );
if( $products ) {
$product = reset( $products );
return $product->id;
}
}
return false;
}
}
new Misha_Sync_Woo_Products;
Please keep in mind the following:
- Line
5
. We have a choice to use eithersave_post
orsave_post_{post type}
hook, I recommend the second one because it allows us to skip one more conditional statement. - Line
25
.$stores = array( … )
– is what we did in the previous step. You have to provide application passwords here. - Line
28
.$product->get_data()
is an amazing method ofWC_Product
class that allows to get all the product information and pass it almost without changes into a REST API request. - Lines
34-41
. More about sending API requests with product images you can find in this tutorial or take a look at the plugin solution. - Lines
49-53
. I am pretty sure you don’t want product duplicates to be created on other stores every time you hit “Update” button, so I created a methodproduct_exists()
that performs one more REST API request in order to check whether a product with the same SKU already exists or not. But it is also possible to do with product meta data (and faster).
Using my plugin
If the above code doesn’t fit your needs, you can configure WooCommerce API product sync with my Simple WordPress Crossposting plugin.
Some of the plugin features:
- Product images and product gallery images are fully supported.
- Easily add stores to sync products with in plugin settings.
- Sync not only simple but also variable products with their variations.
- Sync meta fields (ACF etc).
- Exclude specific product data from the syncronisation (e.g. product stock information).
Please let me know in comments if you have any questions about the code or about my plugin.

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