SafeSale · Blog

Measuring a Shopify sale: what the reports see and what they miss

Why a sale run as an automatic discount shows up in Shopify's discount reports and a price rewrite does not, and how to measure margin either way.

The sale is over, prices are back, and someone asks the obvious question: did it work? For a lot of stores the honest answer is a shrug. Revenue went up, that is visible on the dashboard, but how much of that revenue was margin given away, which products carried the sale and whether the discount paid for itself are questions that depend entirely on how the sale was run. Two sales that looked identical to shoppers can leave completely different traces in Shopify's reports.

This post explains what Shopify records when a sale runs as an automatic discount versus as a rewrite of the product price, which reports can see each one, and how to get a margin figure in either case. It is written around SafeSale, which supports both methods, but the reporting behaviour is Shopify's and applies to any tool.

Two sales, two paper trails

When a sale runs as an automatic discount, the product price in the catalog never changes. The shopper sees the reduction in the cart and at checkout, and the order Shopify creates records three things: the original line price, the amount taken off, and a discount application with a title. That title is how the order knows which promotion touched it, and it is what the reporting layer groups on.

When a sale runs as a price rewrite, the product price itself is lowered and the old price is moved into the compare-at field so the theme can show the strike-through. The order Shopify creates records a product sold at its price, full stop. There is no discount line, because from Shopify's point of view no discount happened. The compare-at price is a storefront display value; it is not copied onto orders and it does not feed reports. The two methods are compared in full in automatic discount vs compare-at price rewrite; this post is only about what each leaves behind.

What the automatic discount mode writes

SafeSale creates its automatic discount through Shopify's Functions API, and the title it gives the discount is the name you typed into the sale editor. That is a deliberate choice: if you call the sale "Spring 20% off knitwear", that exact string appears on every order it reduced, in the order's discount section in the admin, in the order export under the discount columns, and in Shopify's discount reports. Name sales the way you want to read them in a report six months later. "Sale 3" will not help you then.

Shopify's sales reports include a report of sales by discount, which lists each discount title with the orders, units and discounted amount attributed to it. Because the title is the sale name, a SafeSale sale appears as one row, and a store that ran three overlapping sales gets three rows. Three practical notes on reading it:

  • The discount amount is what was taken off, not what was earned. A row showing 4,000 in discounts against 16,000 in sales means shoppers paid 16,000 for goods that would have listed at 20,000. Whether that was a good trade depends on your cost of goods, which the discount report does not know.
  • Order-level discounts share the credit. A SafeSale discount is created to combine with order discounts and shipping discounts but not with other product discounts. An order that used your sale and a free shipping code shows both titles, and the free-shipping row is counted on the same order. Sum the rows and you will double-count orders; look at each row on its own instead.
  • Returns come off later. A return processed after the sale ended reduces net sales in the period the return was made, not the period the sale ran. Pull the report again two or three weeks after the sale for a settled number.

If you ran an A/B test on the discount depth, the sale page in SafeSale has its own report, built from the web pixel rather than from Shopify's reports; how that works and what its sample sizes mean is in A/B testing discount depth. The two sources should agree on direction and will rarely agree to the unit, because the pixel counts sessions it saw and reports count orders.

What the price rewrite mode writes

Nothing, as far as discount reporting is concerned. The sales reports for the period show more units and lower average prices on the affected products, and that is the entire signal. There is no row to find, no discount column to sum and no way for Shopify to tell you which orders were influenced by the sale rather than merely placed during it.

This is not a flaw in the rewrite method so much as a property of it, and it is the main reason to pick automatic discounts when you can. Rewrite exists for the cases where a discount cannot reach: channels and feeds that read the product price directly, themes that must show a was/now price on the product page without any app script, and the other situations listed in the comparison post. If you are in one of them, you can still measure the sale; you just have to bring the original prices yourself.

Getting a margin number from a rewrite sale

Every SafeSale sale keeps a snapshot of each variant's price and compare-at price as they were before the sale touched them. The snapshot is what the revert uses, which is covered in how sale apps leave wrong prices behind, but it is also a record of the original prices with timestamps, and the sale page lets you download it as a CSV. The columns are the product and variant IDs in both their numeric and GraphQL forms, the original price, the original compare-at price, the time the sale price was applied, and the time it was restored.

  1. Download the snapshot CSV from the sale page once the sale has ended and the restore has completed, so every row has both timestamps.
  2. Export orders from the Shopify admin for the window between the earliest applied time and the latest restored time. The order export includes a line per item with the variant ID and the price paid.
  3. Join the two on variant ID. For each order line, the markdown is the snapshot's original price minus the price paid, times the quantity. Lines for variants not in the snapshot were not on sale, and lines placed before the applied time or after the restored time for that variant were not either; the rollout to a large catalog takes minutes, so the timestamps differ by variant.
  4. Sum the markdown and compare it with the extra units sold against the prior period. Subtract cost of goods from both periods and you have the number the discount report would have given you for free.

Fifteen minutes in a spreadsheet, once per sale. If that sounds like more than you want to do regularly, it is a point in favour of the automatic discount mode for your next sale.

Comparisons that mislead

  • Sale week against the week before, when the week before was quiet because you announced the sale early. Shoppers wait. Compare against the same week last year or against a four-week average instead.
  • Revenue without units. A 25% discount that lifts revenue 10% sold a lot more boxes for less money each. Shipping, handling and returns scale with boxes, not revenue.
  • Counting every order during the sale as a sale order. In automatic discount mode, filter by the discount title. Orders with no sale items, gift cards, and full-price add-ons were going to happen anyway. Gift cards never carry the discount at all, as the gift card post explains.
  • Mixing tax settings. Some reports show amounts including tax and some excluding it. A sale run in a market with 20% VAT looks very different depending on which you picked.

Before the next sale

Three small decisions made before the sale make the measurement afterwards almost automatic. Name the sale as the report row you want to read. Prefer the automatic discount mode unless a channel forces the rewrite. And note the comparison period you intend to use before the sale runs, so you are not choosing the flattering one afterwards. SafeSale keeps the sale name on every order it touches and the snapshot for every price it changes; what you do with them is the part that makes the next sale better than this one.

Frequently asked questions

Does a SafeSale sale appear in Shopify's Sales by discount report?

In automatic discount mode, yes. Every order that received the sale carries a discount allocation whose title is the name you gave the sale in SafeSale, and Shopify's discount reports group by that title. In price-rewrite mode, no: the order simply records the lower price, and Shopify has no discount to report on.

How do I work out the margin given away by a price-rewrite sale?

Export the sale's snapshot CSV from the sale page. It lists every variant's original price and compare-at price with the time the sale price was applied and restored. Join it to an orders export for the sale window on variant ID, and the difference between the original price and the price paid, multiplied by units, is the markdown.

Why does my sale's revenue look lower than the discount report suggests?

Discount reports measure the amount taken off; sales reports measure what was paid, after returns and before or after tax depending on the report. Compare like with like: pick the sale window, the same channel filter and the same tax setting in both before concluding anything.

Can I compare a sale against the week before it?

Yes, and it is the most useful comparison for a short sale. Pull the same report for the equivalent period before the sale with the same filters, and look at units and gross margin as well as revenue. A sale that lifts revenue 15% while cutting margin 30% is not the result it looks like at first glance.