WooCommerce Create Products
Version | Date | Created / Updated | Notes |
---|---|---|---|
v1.0 | Hristiyan Georgiev | Initial version |
Just like all other product related flows, when we are talking about creating products on WooCommerce we have to consider product creation and variant creation. There are two separate flows for that.
Simple products are straightforward - one API call creates a complete, purchasable product.
Variable products require a two step creation because they're essentially a container(parent) holding multiple variations. For variable products, customers actually purchase the variations, not the parent product. The parent product serves as a container and cannot be purchased directly. This means that because of our structure, we will need to implement a logic and if we have a variation product to be created, we will have to use the product with the lowest ID in MCPro as a parent product and then make sure to send this same product information as variant.
All triggers, validations etc. which are not specifically mentioned, are as per our abstraction for Product Creation. Product Listing general requirements
Product create
API Call : POST /wp-json/wc/v3/products/
API Docs : https://woocommerce.github.io/woocommerce-rest-api-docs/?shell#create-a-product
Example request body :
{
"name": "Hoodie with VariantNEW",
"type": "variable",
"description": "<p style=\"text-align: left;\">Hoody very best and ogod</p>",
"short_description": "The best product in the whole world",
"sku": "STAMAT14213135",
"global_unique_id": "",
"regular_price": "110",
"sale_price": "99",
"date_on_sale_from_gmt": null,
"date_on_sale_to_gmt": null,
"manage_stock": true,
"stock_quantity": 20,
"sold_individually": "true",
"weight": "123",
"dimensions": {
"length": "22",
"width": "12",
"height": "5"
},
"categories": [
{
"id": 15
}
],
"tags": [
{
"name": "blabla"
}
],
"images": [
{
"src": "https://cdn.pixabay.com/photo/2017/08/09/04/53/texture-2613518_1280.jpg"
}
],
"attributes": [
{
"id": 1,
"variation": true,
"visible": true,
"options": [
"S",
"M"
]
}
],
"brands": [
{
"id": 24
}
]
}
Request Mapping :
WooCommerce Field | MCPro Field | Notes | |
---|---|---|---|
name |
Listing > Title |
||
type |
We need a logic. |
-If we are sending a single product, not part of a variation we send this hardcoded as “simple”
-If we are sending a variation, we send this as “variable” |
| description
| | Listing
> Description
| Description is html friendly.
The design template logic remains so if we have design template we pick as per abstraction. |
| short_description
| | Listing WooCommerce
> Short Description
| |
| sku
| | Product
> SKU
| |
| global_unique_id
| | Product Account
>
Marketplace EAN
OR
Product
> EAN
OR
Product
> Barcode
OR
Product
> MPN
OR
Product
>UPC
| Product Account
is with priority. The priority in the other fields is : EAN, Barcode, MPN, UPC |
| regular_price
| | Listing
> RRP
| We need to have a logic and if RRP
is empty, we send Price
as regular_price
and sale_price
as empty.
Please note that we exclude this field when creating a variation parent. |
| sale_price
| | Listing
> Price
| Please note that it looks like Woo does not have any validations for prices whatsoever, so it is possible that we send sale_price
higher than regular_price
. They will still return success response but the higher sale price will not show, only the lower price will show. We want to have internal validation and if we have Price
> RRP
, we want to store internal error.
Please note that we exclude this field when creating a variation parent. |
| date_on_sale_from_gmt
| | Listing WooCommerce
> Promotion Date Start
| We need to convert it into an ISO8601 format. We only need to send if both fields are filled. If one or both are not filled, we exclude them BOTH from the payload.
Please note that we exclude this field when creating a variation parent. |
| date_on_sale_to_gmt
| | Listing WooCommerce
> Promotion Date End
| We need to convert it into an ISO8601 format. We only need to send if both fields are filled. If one or both are not filled, we exclude them BOTH from the payload.
Please note that we exclude this field when creating a variation parent. |
| manage_stock
| | | Hardcoded to “true”.
Please note that we exclude this field when creating a variation parent. |
| stock_quantity
| | Listing
> Quantity
| Please note that we exclude this field when creating a variation parent. |
| sold_individually
| | Listing WooCommerce
> Sold Individually
| New field! This should be a tickbox field and if checked, we send “true”, if unchecked we send “false”. Default should be unchecked.
Please note that we exclude this field when creating a variation parent. |
| weight
| | Product
> Weight
| We need a logic based on the Channel
> Country
:
-If it is United States we need to convert to lbs
-If it is any other country, we send it without converting. |
| dimensions
| | | |
| | length
| Product
> Length
| We need a logic based on the Channel
> Country
:
-If it is United States we need to convert to inches
-If it is any other country, we send it without converting. |
| | width
| Product
> Width
| We need a logic based on the Channel
> Country
:
-If it is United States we need to convert to inches
-If it is any other country, we send it without converting. |
| | height
| Product
> Height
| We need a logic based on the Channel
> Country
:
-If it is United States we need to convert to inches
-If it is any other country, we send it without converting. |
| categories
| | | |
| | id
| Listing
> Primary Category ID
AND
Listing WooCommerce
> More Categories
| If we have more than one category we need to send them as separate objects. If we have more than 1 record in More Categories
they will be in a table field style. We need to split and send them as separate objects. We will have the name filled but we need to send the id
. We need to have the same taxonomy validation for More Categories
as Primary Category ID
. |
| tags
| | | |
| | name
| Listing WooCommerce
> Tags
| New field! This should be a field with the possibility to “add more features”. Similar to PictureUrls fields. The only difference is that here we will have only one box for input instead of two. Then each row should be sent as separate object into the tags
array |
| images
| | | |
| | src
| Listing Image + More Images
OR
All Images (Leading + Additional | As per abstraction - Images Handling Additional Explanation
We need to have a logic
-If we are creating a simple product, we send All Images
-If we are creating a variation parent, we send Listing Image + More Images |
| attributes
| | | Each separate attribute needs to be in a separate attributes array |
| | id
| Listing
> Item Specific Name
| |
| | variation
| | -If we are creating simple product, we send this as “false”
-If we are creating a variation parent, we send this as “true” |
| | visible
| | Hardcoded as “true” |
| | options
| Listing
> Item Specific
Value
| Please note that if we are sending a variation parent, we MUST include all of the variations’ item specific attributes. So for example if we have a variation that varies by Size and we have values of S,M,L across the variation products, then we need to include them all when creating the variation parent (just like in the payload example.
If it is a simple product, it will have just one option and we still need to add it in the options
array. |
| brands
| | | Each separate brand (if more than 1) needs to be in a separate brands array |
| | id
| Product
> Brand
AND
Listing
WooCommerce
> More Brands
| If we have more than one brand we need to send them as separate objects. If we have more than 1 record in More Brands
they will be separated by comma. We need to split and send them as separate objects. We will have the name filled but we need to send the id
. We need to have the same taxonomy validation for More Brandss
as Product
> Brand
|
Example Success Response :
{
"id": 130,
"name": "Hoodie with VariantNEW",
"slug": "hoodie-with-variantnew-3",
"permalink": "https://sociable-sable-b1ae91.instawp.xyz/product/hoodie-with-variantnew-3/",
"date_created": "2025-05-29T14:38:03",
"date_created_gmt": "2025-05-29T14:38:03",
"date_modified": "2025-05-29T14:38:03",
"date_modified_gmt": "2025-05-29T14:38:03",
"type": "variable",
"status": "publish",
"featured": false,
"catalog_visibility": "visible",
"description": "<p style=\"text-align: left\">Hoody very best and ogod</p>",
"short_description": "The best product in the whole world",
"sku": "STAMAT14213135",
"price": "",
"regular_price": "",
"sale_price": "",
"date_on_sale_from": null,
"date_on_sale_from_gmt": null,
"date_on_sale_to": null,
"date_on_sale_to_gmt": null,
"on_sale": false,
"purchasable": false,
"total_sales": 0,
"virtual": false,
"downloadable": false,
"downloads": [],
"download_limit": -1,
"download_expiry": -1,
"external_url": "",
"button_text": "",
"tax_status": "taxable",
"tax_class": "",
"manage_stock": true,
"stock_quantity": 20,
"backorders": "no",
"backorders_allowed": false,
"backordered": false,
"low_stock_amount": null,
"sold_individually": true,
"weight": "123",
"dimensions": {
"length": "22",
"width": "12",
"height": "5"
},
"shipping_required": true,
"shipping_taxable": true,
"shipping_class": "",
"shipping_class_id": 0,
"reviews_allowed": true,
"average_rating": "0",
"rating_count": 0,
"upsell_ids": [],
"cross_sell_ids": [],
"parent_id": 0,
"purchase_note": "",
"categories": [
{
"id": 15,
"name": "Uncategorized",
"slug": "uncategorized"
}
],
"tags": [
{
"id": 41,
"name": "blabla",
"slug": "blabla"
}
],
"images": [
{
"id": 129,
"date_created": "2025-05-29T14:38:02",
"date_created_gmt": "2025-05-29T14:38:02",
"date_modified": "2025-05-29T14:38:03",
"date_modified_gmt": "2025-05-29T14:38:03",
"src": "https://sociable-sable-b1ae91.instawp.xyz/wp-content/uploads/2025/05/texture-2613518_1280-16.jpg",
"name": "texture-2613518_1280-16.jpg",
"alt": ""
}
],
"attributes": [
{
"id": 1,
"name": "Size",
"slug": "pa_size",
"position": 0,
"visible": true,
"variation": true,
"options": [
"M",
"S"
]
}
],
"default_attributes": [],
"variations": [],
"grouped_products": [],
"menu_order": 0,
"price_html": "",
"related_ids": [
97,
95,
114,
120,
93
],
"meta_data": [],
"stock_status": "instock",
"has_options": true,
"post_password": "",
"global_unique_id": "",
"permalink_template": "https://sociable-sable-b1ae91.instawp.xyz/product/%pagename%/",
"generated_slug": "hoodie-with-variantnew-3",
"brands": [
{
"id": 24,
"name": "Guma",
"slug": "guma"
}
],
"_links": {
"self": [
{
"href": "https://sociable-sable-b1ae91.instawp.xyz/wp-json/wc/v3/products/130",
"targetHints": {
"allow": [
"GET",
"POST",
"PUT",
"PATCH",
"DELETE"
]
}
}
],
"collection": [
{
"href": "https://sociable-sable-b1ae91.instawp.xyz/wp-json/wc/v3/products"
}
]
}
}
Example Error Response :
{
"code": "product_invalid_sku",
"message": "Invalid or duplicated SKU.",
"data": {
"status": 400,
"resource_id": 130,
"unique_sku": "STAMAT14213135-1"
}
}
From the success response we only need to store the id
. If we have created a simple product, want to store it into Listing
> Channel Item ID
and mark the product as created as per the abstraction Product Listing general requirements
If we have created a variation parent, we want to mark it as such, by raising a flag in Listing WooCommerce
> Is Parent
(new field) and store the id
into Listing
> Channel Item ID
. This way we will use the field as a marker and identify that this product is the variation parent and that we still need to send its’ product information when creating the variants. At this point we don’t want to touch any statuses on the product.
To hopefully make it easier to understand, here is an example : We have a variation group consisting of SKUs 1, 2, 3, 4, and 5. Our goal is to create this variation in WooCommerce. During the product creation process, we select the parent SKU (which should be SKU 1, the one with the lowest ID) and initiate its creation. Once it’s successfully created, we store its ID in the two fields mentioned earlier. Now, we need to add the variants. Since SKU 1 serves only as the parent and cannot be purchased on its own, we must include it in the add variation SKUs call. Therefore, we send SKUs 1, 2, 3, 4, and 5 for variation creation. There are some specific details to consider when sending the parent SKU as a variant, which will be explained in the mapping section below.
From the error response we want to store the message
into Listing
> Update Item Error
and set the Listing
> List/Update the whole item
= Error.
Variant Create
After successfully creating the parent, we now need to add the variants in it. Remember we also need to send the parent’s information as a variant in order for it to be sold.
For this call we need to pick all of the variants that are part of the parent’s variation (which we have already created) and push them for creation. In order to identify whether we have variations to create, we want to have a few checks : whether the product is part of a variation group and whether we have created a parent within that variation group. If these two conditions match, this means we have to create a variation.
API Call : POST /wp-json/wc/v3/products/{productId}/variations
API Docs : https://woocommerce.github.io/woocommerce-rest-api-docs/?shell#create-a-product-variation
The {productId}
we pick from the parent’s Listing
> Channel Item ID
.
Example call :
{
"name": "Hoodie with Variant",
"description": "<p style=\"text-align: left;\">Hoody very best and ogod</p>",
"sku": "stana123",
"global_unique_id": "",
"regular_price": "110",
"sale_price": "99",
"date_on_sale_from_gmt": null,
"date_on_sale_to_gmt": null,
"manage_stock": true,
"stock_quantity": 20,
"weight": "123",
"dimensions": {
"length": "22",
"width": "12",
"height": "5"
},
"categories": [
{
"id": 15
}
],
"tags": [],
"images": [
{
"src": "https://cdn.pixabay.com/photo/2017/08/09/04/53/texture-2613518_1280.jpg"
}
],
"attributes": [
{
"id": 1,
"option": "S"
}
],
"brands": [
{
"id": 24
}
]
}
Call Mapping :
WooCommerce Field | MCPro Field | Notes | |
---|---|---|---|
name |
Listing > Title |
||
description |
Listing > Description |
Description is html friendly. | |
The design template logic remains so if we have design template we pick as per abstraction. | |||
sku |
Product > SKU |
Please note. If we are sending the parent’s info, we need to send this as empty. We can find out if we are sending the parent’s details if we have Listing WooCommerce > Is Parent flag to Yes. |
|
global_unique_id |
Product Account > |
Marketplace EAN
OR
Product > EAN
OR
Product > Barcode
OR
Product > MPN
OR
Product >UPC |
Product Account is with priority. The priority in the other fields is : EAN, Barcode, MPN, UPC.
Please note. If we are sending the parent’s info, we need to send this as empty. We can find out if we are sending the parent’s details if we have Listing WooCommerce > Is Parent flag to Yes. |
regular_price |
Listing > RRP |
We need to have a logic and if RRP is empty, we send Price as regular_price and sale_price as empty. |
||
---|---|---|---|---|---|---|
sale_price |
Listing > Price |
Please note that it looks like Woo does not have any validations for prices whatsoever, so it is possible that we send sale_price higher than regular_price . They will still return success response but the higher sale price will not show, only the lower price will show. We want to have internal validation and if we have Price > RRP , we want to store internal error. |
||||
date_on_sale_from_gmt |
Listing WooCommerce > Promotion Date Start |
We need to convert it into an ISO8601 format. We only need to send if both fields are filled. If one or both are not filled, we exclude them BOTH from the payload. | ||||
date_on_sale_to_gmt |
Listing WooCommerce > Promotion Date End |
We need to convert it into an ISO8601 format. We only need to send if both fields are filled. If one or both are not filled, we exclude them BOTH from the payload. | ||||
manage_stock |
Hardcoded to “true”. | |||||
stock_quantity |
Listing > Quantity |
|||||
sold_individually |
Listing WooCommerce > Sold Individually |
New field! This should be a tickbox field and if checked, we send “true”, if unchecked we send “false”. Default should be unchecked. | ||||
weight |
Product > Weight |
We need a logic based on the Channel > Country : |
-If it is United States we need to convert to lbs -If it is any other country, we send it without converting. | dimensions |
length |
Product > Length |
We need a logic based on the Channel > Country :
-If it is United States we need to convert to inches
-If it is any other country, we send it without converting. |
width |
Product > Width |
We need a logic based on the Channel > Country :
-If it is United States we need to convert to inches
-If it is any other country, we send it without converting. |
height |
Product > Height |
We need a logic based on the Channel > Country :
-If it is United States we need to convert to inches
-If it is any other country, we send it without converting. |
categories |
id |
Listing > Primary Category ID
AND
Listing WooCommerce > More Categories |
If we have more than one category we need to send them as separate objects. If we have more than 1 record in More Categories they will be separated by comma. We need to split and send them as separate objects. We will have the name filled but we need to send the id . We need to have the same taxonomy validation for More Categories as Primary Category ID . |
tags |
name |
Listing WooCommerce > Tags |
New field! This should be a field with the possibility to “add more features”. Similar to the Item/Variation specifics fields. The only difference is that here we will have only one box for input instead of two. Then each row should be sent as separate object into the tags array |
images |
src |
Main Image | As per abstraction - Images Handling Additional Explanation | ||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
attributes |
Each separate attribute needs to be in a separate attributes array | |||||||||||||||||||||||||||||||||||||||||||||||||
id |
Listing > Item Specific Name |
We will have the name, but we send the id |
||||||||||||||||||||||||||||||||||||||||||||||||
option |
Listing > Item Specific Value |
|||||||||||||||||||||||||||||||||||||||||||||||||
brands |
Each separate brand (if more than 1) needs to be in a separate brands array | |||||||||||||||||||||||||||||||||||||||||||||||||
id |
Product > Brand |
AND
Listing
WooCommerce
> More Brands
| If we have more than one brand we need to send them as separate objects. If we have more than 1 record in More Brands
they will be separated by comma. We need to split and send them as separate objects. We will have the name filled but we need to send the id
. We need to have the same taxonomy validation for More Brands
as Product
> Brand
|
Example Success Response :
{
"id": 139,
"type": "variation",
"date_created": "2025-06-02T08:28:17",
"date_created_gmt": "2025-06-02T08:28:17",
"date_modified": "2025-06-02T08:28:17",
"date_modified_gmt": "2025-06-02T08:28:17",
"description": "<p style=\"text-align: left\">Hoody very best and ogod</p>\n",
"permalink": "https://sociable-sable-b1ae91.instawp.xyz/product/hoodie-with-varianto-2/?attribute_pa_size=xs",
"sku": "stan111133",
"global_unique_id": "",
"price": "99",
"regular_price": "110",
"sale_price": "99",
"date_on_sale_from": null,
"date_on_sale_from_gmt": null,
"date_on_sale_to": null,
"date_on_sale_to_gmt": null,
"on_sale": true,
"status": "publish",
"purchasable": true,
"virtual": false,
"downloadable": false,
"downloads": [],
"download_limit": -1,
"download_expiry": -1,
"tax_status": "taxable",
"tax_class": "parent",
"manage_stock": true,
"stock_quantity": 20,
"stock_status": "instock",
"backorders": "no",
"backorders_allowed": false,
"backordered": false,
"low_stock_amount": null,
"weight": "103",
"dimensions": {
"length": "33",
"width": "15",
"height": "157"
},
"shipping_class": "",
"shipping_class_id": 0,
"image": null,
"attributes": [
{
"id": 1,
"name": "Size",
"slug": "pa_size",
"option": "XS"
}
],
"menu_order": 0,
"meta_data": [],
"name": "XS",
"parent_id": 116,
"_links": {
"self": [
{
"href": "https://sociable-sable-b1ae91.instawp.xyz/wp-json/wc/v3/products/116/variations/139",
"targetHints": {
"allow": [
"GET",
"POST",
"PUT",
"PATCH",
"DELETE"
]
}
}
],
"collection": [
{
"href": "https://sociable-sable-b1ae91.instawp.xyz/wp-json/wc/v3/products/116/variations"
}
],
"up": [
{
"href": "https://sociable-sable-b1ae91.instawp.xyz/wp-json/wc/v3/products/116"
}
]
}
}
Example Error Response :
{
"code": "woocommerce_rest_product_variation_invalid_parent",
"message": "Cannot set attributes due to invalid parent product.",
"data": {
"status": 404
}
}
From the success response we only need to store the id
into Listing WooCommerce
> Variant ID
and act as per the abstraction Product Listing general requirements.
From the error response we want to store the message
into Listing
> Update Item Error
and set the Listing
> List/Update the whole item
= Error.