How to Create Product Variations Programmatically

There is also a tutorial on my blog about creating products programmatically where I slightly cover the product variations.

But there are still a couple moments I would like to discuss more thoroughly, I came to them when I was doing the variable products crossposting for my multisite crossposting plugin.


So, that is supposed to be the single good and correct way to create product variations. We are going to use CRUD objects that were introduced in WooCommerce 3.0 (CRUD is an abbreviation for Create, Read, Update, Delete). We covered it a little bit here. The idea is that you create WC_Product_Variation object, configure it with its methods and we are done.

Like that:

$variation = new WC_Product_Variation();
$variation->set_parent_id( $product_id );
$variation->set_attributes( array( 'attribute_magical' => 'Yes' ) );
$variation->set_regular_price( 50 );

In the code sample above you can see the bare minimum of everything:

  • We need set_parent_id() method to connect a variation to a specific product.
  • Any variation is based on a set of product attributes, that’s why set_attributes() method is also required.
  • And also the price, the variation won’t be visible and available without a price, so we’re using set_regular_price() here.
  • And then we… save(), this method is also super-important, because we do not run database queries on every method call.

WC_Product_Variation methods

I completely get it that there is a official WooCommerce documentation out there with all the methods described. In that case I am not going to list all the methods here, I’ll just show you much more detailed code examples like an extended version of a previous one.

So, the first one is for downloadable and virtual variations:

$variation = new WC_Product_Variation();
$variation->set_parent_id( $product_id );
$variation->set_attributes( array( 'attribute_magical' => 'Yes' ) );

$variation->set_sku( 'MISHA-123' );
$variation->set_image_id( $featured_image_id );

// some basics
$variation->set_downloadable( true ); // default – false
$variation->set_virtual( true ); // default – false

$variation->set_stock_status( 'instock' ); // outofstock, onbackorder

// prices
$variation->set_regular_price( 40 );
$variation->set_sale_price( 10 );
$variation->set_date_on_sale_from( '2022-01-01' );
$variation->set_date_on_sale_to( '2023-01-01' );

$variation->set_description( 'Programmatically created variation' );

// downloadable variation
$variation->set_downloads( $downloads );
$variation->set_download_limit( 1 ); // can be downloaded only once
$variation->set_download_expiry( 7 ); // expires in a week


Some moments to remember:

  • When you add attributes when creating a product variation, you have to keep in mind, that these attributes should already be added to the product.
  • You might be wondering, what is inside $downloads variable. Well, an array of WC_Product_Download objects, you can find the detailed description here.

And the second one is for physical variations with shipping and all that stuff:

$variation = new WC_Product_Variation();
$variation->set_parent_id( $product_id );
$variation->set_attributes( array( 'attribute_magical' => 'Yes' ) );

$variation->set_sku( 'MISHA-234' );
$variation->set_image_id( $featured_image_id );

$variation->set_manage_stock( true ); 

$variation->set_stock_quantity( 10 );
$variation->set_backorders( 'yes' ); // 'no', 'notify'
$variation->set_low_stock_amount( 2 );

$variation->set_weight( 10 );
$variation->set_length( 11 );
$variation->set_width( 12 );
$variation->set_height( 13 );

$variation->set_shipping_class_id( 22 );
$variation->set_regular_price( 50 );


How to check if a variation with specific attributes already exists?

That’s the interesting moment. WooCommerce either allows you to create an unlimited amount of product variations with the same attributes or throws an error Fatal error: Uncaught WC_Data_Exception: Invalid or duplicated SKU in case you’re using set_sku() method.

How to deal with that? Or what to do even, if you would like to add a variation or to update its price for example if the variation already exists?

I think the idea lays behind using get_available_variations() method of WC_Product_Variable object. If you know a more simple way, please tell me in comments!

But let’s look what get_available_variations() returns.

$variations = $product->get_available_variations();
print_r( $variations );
Array (
  [0] => Array (
    [attributes] => Array (
      [attribute_pa_color] => coral
      [attribute_magical] => Yes
    [dimensions] => Array (
      [length] => 
      [width] => 
      [height] => 
    [display_price] => 40
    [display_regular_price] => 40
    [price_html] => £40,00
    [sku] => 
    [weight] => 
  [1] => Array ( [...] )
  [2] => Array ( [...] )

In the print_r() above please pay attention to the differences between two attributes – the first attribute name is prefixed with pa_, which means that it is not a custom product attribute but an attribute with its own taxonomy!

Our goal is to either check against the variation attributes $variation[ 'attributes' ] or SKU $variation[ 'sku' ]. If any of those match, then we have to get a variation ID, so we can update it instead of trying to create a new one.

$variation_sku = 'MISHA-234';
$variation_attributes = array( 
	'attribute_pa_color' => 'coral',
	'attribute_magical' => 'Yes' // It is case-sensitive!

$variations = $product->get_available_variations();
foreach( $variations as $variation ) {
		$variation[ 'sku' ] == $variation_sku 
		|| ! array_diff_assoc( $variation_attributes, $variation[ 'attributes' ] )
	) {
		$variation_id = $variation[ 'variation_id' ];
if( ! empty( $variation_id ) ) {
	// Update variation	
} else {
	// Create variation

We used array_diff_assoc() PHP function to compare the two arrays of attributes. It returns an empty array if the two arrays with attributes match. Please keep in mind that is is case-sensitive, but array elements could follow each other in any order.

How to update a specific variation

We’ve already started talking about updating the variations, now let me show how to do that.

$variation = wc_get_product_object( 'variation', $variation_id );
		'regular_price' => 500000,
		'sale_price' => 500,

Two key moments here:

  • We are using wc_get_product_object(), but not wc_get_product() function in order to get a variation object.
  • I also decided to use set_props() method instead of using both set_regular_price() and set_sale_price(), so you can do multiple changes into your variation without using multiple methods.


So, wait, why we’re covering this way? Though it is completely not recommended to use and if WooCommerce is going to move to custom database tables, all that wp_insert_post()-stuff will stop working just at the same moment. Or what?

Anyway sometimes there could be situations when you need an alternate way! So let me just show you how this is supposed to look.

$variation_id = wp_insert_post( 
		'post_title'   => $product_title . ' – Coral',
		'post_content' => '',
		'post_status'  => 'publish',
 		'post_parent'  => $product_id,
		'post_type'    => 'product_variation',
		'comment_status' => 'closed',
		'ping_status' => 'closed',
		'menu_order' => 2,
// sku
update_post_meta( $variation_id, '_sku', 'MISHA-234' );

update_post_meta( $variation_id, '_virtual', 'no' );
update_post_meta( $variation_id, '_downloadable', 'no' );

// attributes
update_post_meta( $variation_id, 'attribute_magical', 'Yes' );
update_post_meta( $variation_id, 'attribute_pa_color', 'coral' );

// product prices
update_post_meta( $variation_id, '_regular_price', 25 );
update_post_meta( $variation_id, '_price', 25 );
update_post_meta( $variation_id, '_sale_price', 50 );

// featured image
update_post_meta( $variation_id, '_thumbnail_id', $featured_image_id );

WC_Product_Variable::sync( $product_id );
  • As you can see, most of the variation information is stored in post meta. All the meta keys are protected (start with _ symbol) except the attributes.
  • First thing worth to remember is that variations are post types product_variation and they can only have two post statuses – either publish for available variations or private for not enabled ones.
  • Variations are just child posts of a product. So, you can see post_parent parameter is crucial.
  • I think it also will be helpful to mention that product types are defined by taxonomy. So, if you’re going to check if a product is a variable type, you can do this thing if( has_term( 'variable', 'product_type', $product_id ) ).
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