Late last year we introduced our roadmap insight for Cost of Goods Sold, aka โCOGSโ, a feature enhancement to core that assesses the true cost of each product sold.
Here is a reminder of how COGS can benefit merchants for reporting, forecasting, inventory and more:
- Calculate exact profit margins for every product and order
- Influence data-driven decisions about pricing, marketing, and inventory management from COGS data
- Track a storeโs financial performance with greater precision for sales planning and seasonal initiatives
How can COGS be implemented and utilized by developers?
As a feature, COGS impacts the merchant backend UI, and introduces programmatic API and REST API changes in its implementation. While the feature can currently be found in the experimental features section, it will be moved out of beta in the WooCommerce 10.0 release. Once enabled you can freely use the feature. You don’t have to worry about any changes to your data upon any backward-incompatible improvements in the 10.0 release and beyond.
Let’s check out how you can get COGS enabled and ready for use and exploration with the guide below!
The following guide written by @konamiman will show you how to:
- Enable COGS
- Set costs for products
- Display costs for orders
- Clarify terminology in classes
- Use classes to handle programmatic changes
- Understand REST API updates
Enable COGS
Support for COGS is disabled by default in WooCommerce. To enable it you can go to WooCommerce โ Settings โ Advanced โ Features and tick the โCost of Goods Soldโ checkbox.

Set costs for products
Once the feature is enabled youโll see that the product editor in the admin area has a new โCost of goodsโ field in the โGeneralโ tab. This will default to zero for all products, you can set any value here and it will be saved together with the rest of the product data.

For variations, the โCost of goodsโ field in โGeneralโ represents a default value: all the existing variations will inherit this cost value by default, but itโs possible to specify a different value for each individual variation by changing the value directly in the cost box.

Variable products get a bulk action to remove the custom cost from all the variations, so that all of them revert to the default cost value:

Display costs for orders
The order editor in the admin area will have an extra โCostโ column for all the product line items in which youโll see the cost associated to that item (the individual product cost times the quantity of the line). There will be a summary โCost Totalโ value next to the total order amount display:

Note: The column that previously had the title โCostโ is now โPriceโ!
If an order has refunds, the โCostโ column will display the combined cost of the refunded items:

Additionally, the “Cost” column has a tooltip that displays the cost per unit of the product:

Clarify terminology for classes
The terms โnominalโ vs โeffectiveโ vs โtotalโ values as the terminology can be confusing. Let’s examine how best to use these terms in regards to programmatic changes.
- The nominal value is the only one thatโs explicitly set (not calculated). Semantically, itโs just a number (or null), and its meaning potentially depends on the type of product and other per-product or store-wide configuration.
- The effective value is the nominal value converted to a monetary value. As of now, the effective value is always the nominal value, or zero for null nominal values. In future iterations of the feature we might implement other meanings for nominal values (e.g. a percent of the product price) and then the effective value will be calculated accordingly.
- The total value is the final, actual cost value that applies to the product, and is the value that should be used for any calculations involving costs (itโs used e.g. to calculate aggregate order costs). As of now, this is the same as the effective value, except for variations (as explained above for the
get_cogs_value_is_additive
method).
Currently, zero and null are equivalent for nominal values: setting zero with set_cogs_value
will make get_cogs_value
return null
. All products will have a null
nominal cost value by default.
Use classes to create programmatic changes
As for the code API, there are some additions to the product and order classes so cost values can be handled programmatically.
To start with, use the following snippet to check if the feature is enabled:
wc_get_container()->get(Automattic\WooCommerce\Internal\Features\FeaturesController::class)->feature_is_enabled('cost_of_goods_sold');
Click the drop down arrow to take a closer look to Products, Hooks, and Order Classes:
Products
WC_Product
gets the following public methods:
set_cogs_value( ?float $value ): void
โ sets the nominal cost value for the product.get_cogs_value(): ?float
โ gets the value set withset_cogs_value
.get_cogs_effective_value(): float
โ gets the effective cost value for the product (the nominal value converted to a monetary amount).get_cogs_total_value(): float
โ gets the total cost value for the product (the value that should be used for actual cost calculations).get_cogs_value_is_additive(): bool
โ this method exists in theWC_Product_Variation
class only:- If
true
, thenget_cogs_total_value
will return the parent productโs effective cost value added to the variationโs effective cost value. - If
false
, thenget_cogs_total_value
will simply return the variationโs effective cost value.
- If
โฆand the following protected methods:
adjust_cogs_value_before_set( ?float $value ): ?float
โ runs before the value passed toset_cogs_value
is applied, by default it just converts zeros to null.get_cogs_effective_value_core(): float
โ derived classes willing to use a custom calculation of the effective cost value should override this method.
Note: thereโs currently no UI to change the โcost value is additiveโ setting for variations.
Orders
WC_Abstract_Order
gets the following public methods:
has_cogs(): bool
โ indicates if the current order class handles costs, itโs hardcoded tofalse
and itโs intended to be overridden in derived classes.calculate_cogs_total_value(): float
โ calculates the total cost value for the order and then sets it withset_cogs_total_value
.set_cogs_total_value( float $value ): void
โ sets the total cost value for the order. Normally this method shouldnโt be executed explicitly, but viacalculate_cogs_total_value
instead.get_cogs_total_value(): float
โ returns the value set byset_cogs_total_value
.
โฆand the following protected method:
calculate_cogs_total_value_core(): float
โ performs the actual calculation of the total cost value for the order by adding up the cost for each line item.
The WC_Order
class implements the following method overrides:
has_cogs()
โ returnstrue
.calculate_cogs_total_value_core()
โ calculates the value invoking the parent method, and then subtracts the costs associated to refunds.
The last pieces of the puzzle are order items. WC_Order_Item
has the following public methods:
has_cogs(): bool
โ indicates if the current order item class handles costs, itโs hardcoded tofalse
and itโs intended to be overridden in derived classes.calculate_cogs_value(): bool
โ calculates the cost value for the line item and sets it withset_cogs_value
. Returnstrue
on success orfalse
on error (in case of error the previous value remains unchanged.)set_cogs_value( float $value ): void
โ sets the total cost value for the order. Normally this method shouldnโt be executed explicitly, but viacalculate_cogs_value
instead.get_cogs_value( $context = 'view' ): float
โ returns the value set byset_cogs_value
.get_cogs_value_html(): string
โ returns the cost value in HTML.get_cogs_value_per_unit_tooltip_text(): string
โ returns the text to be included in a tooltip indicating the unit cost for the order item.
โฆand the following protected method:
calculate_cogs_value_core(): ?float
โ performs the actual calculation of the total cost value for the order item. This base method will simply throw an exception, derived classes are expected to override the method with an actual calculation as long as theirhas_cogs
method returnstrue
.
And as you may have guessed already, WC_Order_Item_Product
has a couple of overrides:
has_cogs
returns true.calculate_cogs_value_core
returns the total value of the product times the quantity.
And thatโs it! These building blocks provide the basic functionality to handle costs for products and orders, while being extensible enough to add support for costs in custom product, order and order item classes.
Hooks
By the way, the following hooks related to the feature are available:
woocommerce_save_order_cogs_value
: to modify the value that gets saved to the database for a given order, or to suppress the value saving altogether.
woocommerce_get_product_cogs_total_value
: to customize the value that gets returned by get_cogs_total_value
.
woocommerce_load_product_cogs_value
: to modify the value that gets loaded from the database for a given product.
woocommerce_load_product_cogs_is_additive_flag
: same as above but for the value override flag.
woocommerce_save_product_cogs_value
: to modify the value that gets saved to the database for a given product, or to suppress the value saving altogether.
woocommerce_save_product_cogs_is_additive_flag
: same as above but for the value override flag.
woocommerce_calculated_order_cogs_value
: to customize the value that gets calculated for an order before itโs set.
woocommerce_calculated_order_item_cogs_value
: to modify the value that gets calculated for an order item before itโs set.
woocommerce_load_order_item_cogs_value
: to modify the value that gets saved to the database for a given order item, or to suppress the value saving altogether.
woocommerce_save_order_item_cogs_value
: to modify the value that gets saved to the database for a given order item, or to suppress the value saving altogether.
woocommerce_load_order_cogs_value
: to modify the value that gets saved to the database for a given order, or to suppress the value saving altogether.
REST API changes
The following is added to the REST API schema for products and variations (/wp-json/wc/v3/products/<product id>
and /wp-json/wc/v3/products/<product id>/variations
endpoints, for both GET
and POST
requests):
{ "cost_of_goods_sold": { "values": [ { "defined_value": <float> "effective_value": <float> } ], "total_value": <float>, "defined_value_is_additive": <bool> } }
defined_value_is_additive
is present only for variations.
effective_value
and total_value
are read-only fields.
In principle, for POST
endpoints only one object with values is expected to be found inside values
.
However the endpoints code will add the defined_values
of all objects if more than one is supplied; the result will be the value set for the product.
The POST
endpoints will not modify the current value of a product being modified if no values
key (or no cost_of_goods_sold
key) is present, but an empty values
array will set a value of zero.
Similarly, for variations the โvalue overrides parentโ flag will not be modified if no defined_value_overrides_parent
is present. Default values for new products are a cost of zero and no override for variations.
As for products, the following is added to the REST API schema for the get order(s) endpoint (/wp-json/wc/v3/orders
and /wp-json/wc/v3/orders/<order_id>
, GET
only):
{ "cost_of_goods_sold": { "total_value": <float> }, "line_items": [ { "cost_of_goods_sold": { "value": <float> } } ] }
These new items appear only when the feature is enabled.
Thereโs no explicit support for altering order and order items COGS values in the REST API, but any request that triggers a orderโs calculate_totals
will cause a recalculation of the COGS values as well.
Finally, the data returned by the order refunds endpoint (/wp-json/wc/v3/orders/<order id>/refunds
) will include cost data (as negative values) for both the refund and the refund items, with the same format as for orders.
Reminder: While the feature can currently be found in the experimental features section, it will be moved out of beta in the WooCommerce 10.0 release.
Leave a Reply