How profit and margin are calculated
A report is a set of decisions about what to count. Here are all of ours, in the order they affect the figure on the screen, with a worked example on the same demo data the screenshots use.
Updated 2026-10-11
Revenue
The net line total of the goods sold, after discounts, excluding tax and excluding shipping.
Tax is not yours, so it is out. Shipping is out because the core records no cost against a shipping line: counting the charge as revenue while its cost sits nowhere would make every margin look better than it is.
Coupons are already applied — WooCommerce stores line totals after discounts, so a 20% code shows up as lower revenue rather than as a separate deduction.
Cost of goods
The sum of the costs recorded on the sold items, read from the order lines, not from today’s product records.
This is the part that makes the report worth building: WooCommerce writes the cost onto the order line at the moment of sale, so an order from last March reports what the goods cost last March. The only thing that overwrites it is a refund, which is core behaviour explained separately.
Gross profit
gross profit = revenue on items with a known cost − cost of those items
Note the first half. Profit is calculated only on items whose cost is known. An item with no recorded cost still counts towards revenue, but it never enters the profit calculation as if the goods had been free.
The alternative — treating a missing cost as zero — produces a bigger, friendlier and completely false profit figure. That is the single most common way a profit report lies to a shop.
Margin
margin = gross profit ÷ revenue on items with a known cost
Profit divided by revenue, not markup on cost. A product bought for 60 and sold for 100 has a 40% margin here. Its markup is 66.7%, and suppliers usually quote that one — the margin and markup calculator converts between them.
Coverage — the number that qualifies every other number
coverage = revenue on items with a known cost ÷ total revenue
Printed at the bottom of the summary panel as a sentence rather than a badge:
Cost is known for 74% of revenue. Profit and margin above are calculated on that part only; zł1,119,760.42 of revenue has no cost recorded.
Coverage is not a score to get to 100%. A shop that gives away samples, or sells goods bought before the cost field existed, will sit below 100% forever — and that is the correct answer rather than a defect.
A worked example
The demo shop on the sample report, over twelve months:
| Figure | Value | Where it comes from |
|---|---|---|
| Revenue | zł4,306,235.11 | all counted order lines |
| Revenue with a known cost | zł3,186,474.69 | revenue minus the uncovered part |
| Revenue with no cost recorded | zł1,119,760.42 | the 26% |
| Cost of goods | zł1,646,293.79 | costs recorded on those lines |
| Gross profit | zł1,540,180.90 | 3,186,474.69 − 1,646,293.79 |
| Margin | 48.3% | 1,540,180.90 ÷ 3,186,474.69 |
| Coverage | 74% | 3,186,474.69 ÷ 4,306,235.11 |
Two things are worth noticing. Margin is computed on covered revenue, not on total revenue — otherwise it would silently drop by a quarter for a reason that has nothing to do with your prices. And the profit figure is smaller than “revenue minus cost” on the whole shop would suggest, because a quarter of the revenue is simply not part of the calculation.
Which orders are counted
Completed, processing and refunded. Cancelled, failed, pending, draft and on-hold orders are not counted at all — not as zero-profit sales, just absent.
Refunded belongs on that list for a reason that is easy to miss: a full refund flips the original
order to refunded, so excluding that status would delete the sale while keeping the refund, and
the order would count as minus its own value. The whole
argument.
Refunds
A refund counts as a negative amount in the period it was issued, not in the period of the original sale. A September sale refunded in November leaves September exactly as it was.
Both sides move: the refund reduces revenue and recorded cost together, so a fully refunded order nets to zero rather than to minus its own margin.
What is deliberately left out
- Shipping income and shipping cost. No cost field exists for a shipping line.
- Payment and gateway fees. They are not in the order data in any standard form.
- Orders in another currency. Named in a warning and excluded, with no exchange rates applied. Why.
- Anything outside the goods — rent, salaries, ads. This is a gross profit report, and gross profit is a real, useful number precisely because its boundary is clear.
Categories overlap on purpose
A product in three categories is counted in all three, so the category rows can add up to more than the summary. WooCommerce has no primary category, so the alternative would be inventing one. The details.
How far back it reaches
There is no window. The report counts the dates you pick — the shortcuts stop at twelve months, but a custom range takes any two dates, back to your oldest order. Nothing is clamped on the way to the query, and the two date fields always show the days actually counted.
What ends the history is the cost data rather than the range: revenue reaches back to the oldest order, while profit and margin reach back only to the day costs started being recorded. How far back the report reaches.
Where these numbers come from in the database
For anyone who wants to check the arithmetic against their own SQL:
- product cost —
_cogs_total_valuein postmeta, pluswc_product_meta_lookup.cogs_total_value, which isNULLwhen no cost is entered; - order line cost —
_cogs_valueinwoocommerce_order_itemmeta, the same table under HPOS and classic storage; - order total cost —
_cogs_total_valueinwc_orders_metaunder HPOS, in postmeta otherwise; - line revenue — the line item’s net total, from the same order items tables.
The report is calculated by your own database and never leaves your server. If a figure here does not match what you get by hand, that is worth an email — a wrong number is the only real failure this kind of product can have.