Create Products Programmatically
In this tutorial, I am going to guide you through the process of creating products in WooCommerce in code. The same way of creating products I use when crossposting products between stores in a multisite network, so, in case you need more examples, you can check out this article or my crossposting plugin.
It is the most correct way of creating products in the latest WooCommerce versions. So, please forget about wp_insert_post() and update_post_meta() functions. Yes, I perfectly remember that products in WooCommerce are (still) WordPress custom post types and product prices are post meta but it doesn’t allow us to use those functions anyway, because there is a little more than that and it could cause bugs.
In this tutorial, we are going to use CRUD objects that were introduced in WooCommerce 3.0 (CRUD is an abbreviation of Create, Read, Update, Delete). The CRUD objects we are interested in right now are:
WC_Product_Simple– for simple products,WC_Product_External– for external products,WC_Product_Grouped– for grouped products,WC_Product_Variable– variable ones.
We are also going to use plenty of methods of these objects, that allow us to configure our product data.
Simple Products
Let’s start with creating a simple product. I am suggesting you imagine, what an example simple product should have. Well, it starts with the product name, slug (for URL), price, and… image, right? Okay, we may have a product description with some nice sale lines as well!
// that's a CRUD object
$product = new WC_Product_Simple();
$product->set_name( 'Wizard Hat' ); // product title
$product->set_slug( 'medium-size-wizard-hat-in-new-york' );
$product->set_regular_price( 500.00 ); // in current shop currency
$product->set_short_description( '<p>Here it is... A WIZARD HAT!</p><p>Only here and now.</p>' );
// you can also add a full product description
// $product->set_description( 'long description here...' );
$product->set_image_id( 90 );
// let's suppose that our 'Accessories' category has ID = 19
$product->set_category_ids( array( 19 ) );
// you can also use $product->set_tag_ids() for tags, brands etc
$product->save();The code above is enough to create a product like this.

There are also methods that you could find useful:
set_featured()– passtrueif you want this product to be marked as featured.set_gallery_image_ids()– multiple image IDs can be passed as an array here.set_menu_order()– manual product order as an integer.set_status()– you can set a post status using this method. Don’t want the product to be published? Setdrafthere.set_total_sales()– product total sales can be passed here as an integer value.set_catalog_visibility()– visibility in the catalog can be configured with this method,hidden,visible,searchandcatalogvalues are accepted.update_meta_data()– any product meta data, pass a pair of meta key and meta value as first and second parameters appropriately.
Products on Sale
You can use it in combination with the snippet above, but please don’t forget to add these methods before $product->save(); line.
$product->set_regular_price( 500.00 );
$product->set_sale_price( 250.00 );
// sale schedule
$product->set_date_on_sale_from( '2022-05-01' );
$product->set_date_on_sale_to( '2022-05-31' );
Inventory Settings
$product->set_sku( 'wzrd-hat' ); // Should be unique
// You do not need it if you manage stock at product level (below)
$product->set_stock_status( 'instock' ); // 'instock', 'outofstock' or 'onbackorder'
// Stock management at product level
$product->set_manage_stock( true );
$product->set_stock_quantity( 5 );
$product->set_backorders( 'no' ); // 'yes', 'no' or 'notify'
$prodict->set_low_stock_amount( 2 );
$product->set_sold_individually( true );The parameters above lead to this Inventory configuration.

Dimensions and Shipping
$product->set_weight( 0.5 );
$product->set_length( 50 );
$product->set_width( 50 );
$product->set_height( 30 );
$product->set_shipping_class_id( 'hats-shipping' );In case you’re wondering what is a shipping class ID and where you can get it, I recommend you check this tutorial.
Linked Products
There are two methods – set_upsell_ids() and set_cross_sell_ids(), both of them accept an array of product IDs.
$product->set_upsell_ids( array( 15, 17 ) );
// we can do the same for cross-sells
// $product->set_cross_sell_ids( array( 15, 17, 19, 210 ) );
Attributes
This is going to be interesting.
First of all, I want to remind you that there are two types of product attributes in WooCommerce – predefined taxonomy-based attributes (which can be created in Products > Attributes) and individual product attributes.
Second, forget about wp_set_object_terms() here.
No matter what type of attribute you are going to add to a product, you have to use the single method which is set_attributes(). But what should we pass into it? Below is an example of how to use WC_Product_Attribute class in order to add a custom or taxonomy-based attribute to a product programmatically.
// that's going to be an array of attributes we add to a product programmatically
$attributes = array();
// add the first attribute
$attribute = new WC_Product_Attribute();
$attribute->set_name( 'Magical' );
$attribute->set_options( array( 'Yes', 'No' ) );
$attribute->set_position( 0 );
$attribute->set_visible( true );
$attribute->set_variation( true );
$attributes[] = $attribute;
// add the second attribute, it is predefined taxonomy-based attribute
$attribute = new WC_Product_Attribute();
$attribute->set_id( wc_attribute_taxonomy_id_by_name( 'pa_color' ) );
$attribute->set_name( 'pa_color' );
$attribute->set_options( array( 29, 31 ) );
$attribute->set_position( 1 );
$attribute->set_visible( true );
$attribute->set_variation( false );
$attributes[] = $attribute;
$product->set_attributes( $attributes );When creating attributes you have to remember two things:
- The difference between creating custom or taxonomy-based attributes is in what you pass into
set_id()andset_name()methods. It should be an attribute taxonomy name inside thewc_attribute_taxonomy_id_by_name()function and just a taxonomy name appropriately. - When you pass term IDs in
set_option()function, you shouldn’t do it too early in the code when taxonomy is not even registered. It also applies to product categories and tags.

Virtual and Downloadable products
Let’s assume that our Wizard Hat is just an illustration that we are going to purchase or an accessory in a game like Lineage II. How to make it virtual and downloadable as well?
It is kind of similar to how we made it for product attributes.
// Virtual product : YES
$product->set_virtual( true );
// Downloadable product : YES
$product->set_downloadable( true );
$downloads = array();
// Creating a download with... yes, WC_Product_Download class
$download = new WC_Product_Download();
$file_url = wp_get_attachment_url( $attachment_id ); // attachmend ID should be here
$download->set_name( 'wizard-hat-illustration' );
$download->set_id( md5( $file_url ) );
$download->set_file( $file_url );
$downloads[] = $download;
$product->set_downloads( $downloads );
$product->set_download_limit( 1 ); // can be downloaded only once
$product->set_download_expiry( 7 ); // expires in a week
External and Grouped products
Creating both external and grouped products programmatically is quite simple. If you read the previous chapter carefully, you won’t have any problems with it at all.
First of all, you have to choose an appropriate product PHP class, which is WC_Product_External for external products and WC_Product_Grouped for grouped ones.
Then you have to use set_button_text() and set_product_url() methods to add a link to a partner website where this external product is listed. For grouped products, you have to use the set_children() method only.
Variable Products
Well, it is going to be interesting again. But as always we will make it as simple as possible.
// Creating a variable product
$product = new WC_Product_Variable();
// Name and image would be enough
$product->set_name( 'Wizard Hat' );
$product->set_image_id( 90 );
// one available for variation attribute
$attribute = new WC_Product_Attribute();
$attribute->set_name( 'Magical' );
$attribute->set_options( array( 'Yes', 'No' ) );
$attribute->set_position( 0 );
$attribute->set_visible( true );
$attribute->set_variation( true ); // here it is
$product->set_attributes( array( $attribute ) );
// save the changes and go on
$product->save();
// now we need two variations for Magical and Non-magical Wizard hat
$variation = new WC_Product_Variation();
$variation->set_parent_id( $product->get_id() );
$variation->set_attributes( array( 'magical' => 'Yes' ) );
$variation->set_regular_price( 1000000 ); // yep, magic hat is quite expensive
$variation->save();
$variation = new WC_Product_Variation();
$variation->set_parent_id( $product->get_id() );
$variation->set_attributes( array( 'magical' => 'No' ) );
$variation->set_regular_price( 500 );
$variation->save();Of course, the WC_Product_Variation class has many more methods, almost like the WC_Product_Simple all of them you could find in official WooCommerce documentation.
But for now – we have this simple variable product in our shop.

Almost forgot, you can set a default variation with the set_default_attributes() method.
$product->set_default_attributes( array( 'magical' => 'No' ) );One more thing, worth noting – what to do if you want to assign global, taxonomy-based attributes to a variation? Here is what:
$variation->set_attributes(
array(
'attribute_pa_magical' => 'yes',
'attibute_pa_color' => 'gray',
)
);Basically, you just need to pass an associative array to the set_attributes() method, using attribute_{$taxonomy_name} as keys and attribute term slugs as values.
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
Thanks for this awesome guide!
I really just used
wp_insert_post()to create Woo products programmatically.Appreciate your sharing!
Thanks Gerald!
I hope
wp_insert_post()is in the past now! ;)Thank you very much !
omg thank you so much for writing the section on adding product attributes with such clarity. I’ve been tearing my hair out all day on the multitudes of ways to do them over on stackoverflow but nobody was utilising the new CRUD methods, which are so much simpler!
Always welcome!
Hi, tell me plz, how to add image by url by this methods? It require image ID, i havent images in the site, i have image in another site, cdn , etc
Hey Jar,
Here is how.
Thank you very much for this!!!!!
How do we check if a product with the same SKU already exists and update it instead of creating a new product post?
Great question! Just published a tutorial about that.
Thank you so much! Really awesome and helpful.
So thorough, simple, and easy to understand! Thank you!
Everything works fine when added as an action in
wp_footer, but when I try to execute$product->save()In a stand-alone execute-once snippet, it gives an error:
“Uncaught Error: Class ‘ActionScheduler_Store’ not found in /home/customer/www/*redacted*/public_html/online-store/wp-content/plugins/woocommerce/src/Internal/ProductAttributesLookup/LookupDataStore.php”
Do you know if there are any file includes needed for the save method to work correctly?
The weird thing is, it actually adds the product. Unfortunately, I’m using it to port products in from another platform, and need to loop through 600+ products.
NM! a simple
add_action( 'plugins_loaded',...did the trickThis is amazing. Saved me from a lot of headache.
I’ve been looking to add an image to an attribute but haven’t been able to find a way yet. Like, I’ve asked user to upload an image – I’ve moved the image upon upload to my website server now I want to show a thumbnail of this image under “Custom Image” attribute on cart and checkout page. I know it is possible, I’ve seen it many times but I just haven’t been able to figure out “how”. haha
Any ideas would be greatly appreciated.
And manyt many thank you for sharing your ideas here. :)
Misha! you are my love. <3
I have solved a lot of things using your articles. Keep it up. You are doing great.
Thank you so much <3 🙏
Another thanks for a smashing good guide to creating products programmatically with Woo.
This is such a great article. Why is it ranked so low on Google? I searched for it after changing several keywords.
First of all, a really good and helpful post! THX
I have a WP multisite setup. How can I create a product in all instances?
Since in the multisite there is a separate prefix for each page (wp_posts, wp3_post, wp4_posts, etc.), the product is only ever created in the post table in which the user is currently located. Since the multisite structure realizes multilingualism, I need the same product on all pages.
Do you have a solution for me?
Hi Martin,
You’re always welcome! I think this plugin should fit you the best.
Hi Misha,
Thank you for the excellent tutorial; your blog is the best for help with Woo and WP.
I had a question about the variable products: if we create multiple products that all have the same attributes, what’s the best way to approach it?
If I keep creating new attributes with every product I create (as in the “Magical Hat” example) wouldn’t that cause a huge mess?
A bit confused, if you could help I’d greatly appreciate it.
Thanks. :)
Hey Nick,
you’re always welcome!
It depends on what kind of attributes you have for your products.
Are these the individual product attributes? – No problems at all, then.
Are these global, taxonomy-based attributes? – You just need to
set_id(),set_name()andset_options()as mentioned here.Thank you very much
Thanks for this series of posts! They are a real life saver.
I have a couple hundred colors added to a Color attribute and am now trying to import products and create variations with those colors. How would you go about creating variations with those existing attributes? I’m not having any luck with $variation->set_attributes as in this example. No error, but the created variation just has no color selected. Do I have to first add all colors I intend to use with set_options()?
Hey George,
you need to use
attribute_pa_coloras a key and a color slug as a valueAnother Magical Snippet from Misha :)
Your snippet blogs are Brief, well documented & verified! Thank you captain!
:)
Hi guys,
if you set main attribute like that:
$attribute->set_name( 'Magical Power' );you should change to slug when you insert variation.
$variation->set_attributes( array( 'magical-power' => 'Yes' ) );otherwise, these attributes will come not selected each of variations.
This is the perfect go to snippet to make product programmatically, thanks!!