Skip to content

Cost Prices and Margin

A cost price is what one unit of a product or variant costs the shop. With cost prices, margin (revenue less cost) can be measured per product and listing (Product Engagement) and listings can be ranked for it (Ranking by What Sells, goal margin). An Enterprise feature (merchandisingRules).

Commercially sensitive

Cost prices, and the margin worked out from them, never leave the platform:

Where Cost or margin?
Admin: Catalogue > Cost prices, margin on Search > Product engagement Yes, to ADMIN, SUPERADMIN and FINANCE only (BadgerWebAuthTools.canSeeCosts). A merchandiser or content editor sees Product engagement without margin: it is removed from the data, not just hidden
REST admin API (/v1/admin/catalogue/product-costs, margin on /v1/admin/search/product-engagement) Yes, admin roles only
Storefront, public REST, product responses No: a Product has no cost field
Search index No. Only rankMargin, a relative, ordinal score, from which no cost can be worked back
Site transfer, product exports No
MCP and other AI tools No: there is no MCP tool for costs, and siteTraffic productEngagement carries no margin
Logs No: counts and ids only; ProductCost.toString() leaves the cost out

This holds by construction: costs live only in the productCosts collection (ProductCost: catalogue, SKU, cost in minor units, who set it and when), read and written only by ProductCostService. Everything that serialises a product can't carry one because the product doesn't have one. CostPricesStayInsideTest fails if Product or Variant gains a cost or margin field or site transfer starts reading costs, and StatisticsToolsTest if the MCP engagement entry gains one.

Setting cost prices

  • By SKU: a product's or a variant's. A variant without its own cost uses its product's (ProductCostService.unitCost).
  • In the site's currency, per unit; 0 to £100,000. A blank (or null) cost clears it. A SKU that isn't in the catalogue is reported, not saved.
  • Admin: Catalogue > Cost prices (/admin/catalogue/costs, see the Catalogue guide): find products, type costs against each product and variant, and save; or import a CSV of sku,cost lines (cost in major units, 12.50; a header line and quoted cells are fine; at most 50,000 lines, 5 MB).
  • REST: GET /v1/admin/catalogue/product-costs?skus=A,B (at most 500; answers with the costs of those that have one and how many SKUs have a cost), PUT /v1/admin/catalogue/product-costs with {"costs": [{"sku": "A", "cost": 4550}, {"sku": "B", "cost": null}]} (minor units; at most 50,000; answers with counts, unknown SKUs and refused entries, never costs).
  • Feeds call ProductCostService.setCosts the same way.

Each save records who made it (updatedBy: a user id, or api:<siteId>).

Margin

When an order is placed (ProductEngagementService.recordOrderPlaced), each line's cost is looked up (the variant's, else the product's), and each product's daily engagement counts:

  • marginRevenue: the revenue of its lines whose cost was known;
  • margin: that revenue less those lines' cost (quantity × unit cost), which can be negative.

So margin uses the cost when the order was placed: changing a cost later doesn't rewrite past margin. The margin rate is margin / marginRevenue, and reports apply it to all the revenue (estimated margin), showing what share of revenue had a known cost. Orders placed before cost prices were set have none.

For ranking, a product's margin rate is smoothed towards the site's with three average orders' worth of revenue, so a product without a cost price counts at the site's margin rate, and a site with no cost prices at all ranks for margin exactly as for revenue. A product sold at a loss scores 0 for margin.

Tests

  • ProductCostServiceImplTest: known and unknown SKUs, variants, clearing, limits, CSV parsing (header, quotes, currency symbols, blanks, bad numbers, too many decimals), coverage.
  • ProductEngagementServiceImplTest: margin from the variant's cost else the product's, uncosted lines, unreadable costs.
  • LearnedRankScorerTest: margin rewards a richer margin, an uncosted product at the site rate, a loss scoring 0.
  • CostPricesStayInsideTest, StatisticsToolsTest: the guards above.
  • CostPricesAdminTest, ProductEngagementRenderTest (margin shown, and dropped for other roles), ProductCostsAdminControllerTest (REST).