The analytics headers on the admin tables

Every KPI tile and chart on the six store tables that carry an Analytics tab — what each figure counts, which ones are snapshots rather than windows, and where a multi-currency catalogue makes a total meaningless.

9 min readUpdated 6 August 2026analytics, kpi, reporting, orders, inventory

Six of the store's seven admin tables carry a second tab beside the list. It draws KPI tiles and charts computed straight from that table — money, backlog, liability, SLA — and none of it appears anywhere else in the admin panel. The store dashboard is a different, much smaller surface, but it is not narrower in what it reads: it joins products, order items, categories, orders and customers, and it is where the units-sold, top-product and top-customer figures these analytics tabs cannot produce actually live.

Table Analytics tab
Orders — /admin/ecommerce/order Yes
Products — /admin/ecommerce/product Yes
Discounts — /admin/ecommerce/discount Yes
Shipping — /admin/ecommerce/shipping Yes
Reviews — /admin/ecommerce/review Yes
Wishlist — /admin/ecommerce/wishlist Yes
Categories — /admin/ecommerce/category No — the category table has no analytics config

Opening it

The tab pair sits at the top right of the table header: Overview (the list) and Analytics. Switching to Analytics reveals a timeframe selector — 24 Hours, 7 Days, 30 Days, 3 Months, 6 Months, 1 Year — which opens on 1 Year.

The tiles are computed by POST /api/admin/analysis, which is gated on access.admin — not on the table's own view.ecommerce.* key. A role that can open and read the orders table but has no access.admin sees the tab and gets an error where the tiles should be. See Permissions and roles.

Results are cached in the browser for 5 minutes per timeframe. Changing a figure in the database and reloading the page will not move a tile inside that window; switch timeframe and back, or wait it out.

Two kinds of figure, and why they will not reconcile

This is the single thing to understand before reading any tile, because two tiles on the same screen can legitimately disagree.

  • Window figures are measured over the timeframe you selected, bucketed by the row's creation date. "Orders Placed" on the 7-day view means orders created in the last 7 days.
  • Snapshot figures ignore the timeframe entirely and measure every matching row that exists right now. "Open Backlog Value" is every PENDING order ever, because an order stuck since March is precisely the one a 7-day window hides.

Snapshot tiles are called out in the tables below. Charts are window figures by default, but a ranked bar chart can declare itself all-time, and three on these screens do: Average Price by Type and Inventory Value by Currency on Products, and Most-Issued Depths (% Off) on Discounts. Those three describe the estate as it stands and do not move with the timeframe selector. Every other chart, including all the time-series and the pies, is a window figure.

The delta chip (the small percentage under a figure) appears on window tiles only, and it compares the second half of the selected window against the first half — not against the previous window of the same length. On the 1-Year view that is roughly the last six months against the six before them. When the earlier half is zero there is no baseline and the chip is omitted rather than printed as +100%.

Snapshot tiles carry no delta chip at all. There is nothing to compare a "right now" figure to.

Tiles where a rise is bad — backlog, overdue, lost revenue, negative reviews — have their chip colours flipped, so red always means "worse".

Money tiles and your currency mix

A product carries its own price and its own currency, and checkout copies both onto the order row. A catalogue priced in more than one currency therefore produces order rows in more than one currency, and a naive SUM(total) across them is nonsense — three 0.01 BTC sales beside one $40 sale add to "40.03".

The tiles handle this in one of two ways, and which one you are looking at is visible on the card:

  • Converted totals. Sums grouped by the currency column, priced into USD one denomination at a time, and rendered with a currency symbol. Gross Revenue, Open Backlog Value, Lost Revenue, Discount Given, Inventory Value on Hand and Fixed-Value Liability are all of these.
  • Undenominated figures. Averages cannot be converted — the mean of 0.01 BTC and $40 is not a price in any currency — so those tiles drop the symbol and show the raw mean of the column. Average Order Value, Average price, and Average Value (Fixed Codes) are all bare numbers.

If the platform has no exchange rate for a currency on the card, that currency is excluded rather than counted as zero, and the card prints Excludes <CODE> — no exchange rate under the figure. When you see that line, the number is a lower bound, not the total.

The ranked bar charts (Settled Revenue by Currency, Inventory Value by Currency) are the opposite view: each bar is in its own units and does not convert, so the bars are not expected to add up to the converted headline beside them.

Orders

/admin/ecommerce/orderAnalytics. This is the money screen.

Tile What it measures Scope
Gross Revenue SUM(total) over COMPLETED orders, converted to USD Window
Average Order Value Mean total over COMPLETED orders — no currency, see above Window
completion rate COMPLETED orders ÷ Orders Placed, both measured over the same window Window
Orders Placed Count of orders created Window
Open Backlog Value SUM(total) over everything still PENDING, converted to USD Snapshot
Average Backlog Age Mean hours since creation over everything still PENDING Snapshot
Stalled Over 48 Hours PENDING orders created more than 48 hours ago Snapshot
Lost Revenue (Cancelled + Rejected) SUM(total) over orders whose status is CANCELLED or REJECTED, converted to USD Window
Time to Completion (All Time) Mean hours from creation to last write, over COMPLETED orders Snapshot
Discount Given SUM(discount) over COMPLETED orders, converted to USD Window

Charts: Orders Placed by Status (Selected Period) (pie over all four statuses), Revenue vs Lost Revenue (stacked bar), Settled Revenue by Currency and Settled Revenue by Settlement Rail (ranked bars over currency and walletType).

Three things about this screen specifically

Lost Revenue is an explicit list, not a negation. It counts status CANCELLED or REJECTED by name. It does not mean "everything that is not completed", so PENDING orders are not in it, and if a fifth status is ever added it will not silently start counting that either.

Time to Completion is an upper bound. The order table has no completedAt column. The tile measures creation → updatedAt, and updatedAt is the last write of any kind. On an order that was marked COMPLETED and never touched again, that is exactly the completion time. On one that was edited afterwards — a shipment assigned, an address corrected — the figure is longer than the truth. Read it as a ceiling.

The two duration tiles are all-time on purpose. Average Backlog Age and Time to Completion both ignore the timeframe selector, which is why their titles say so. Backlog age is a "right now" question anyway; and an average duration re-folded over a window would divide the wrong pair of numbers and print a meaningless figure.

completion rate needs Orders Placed. The rate divides completed orders by orders placed in the same window. That is why Orders Placed sits beside it as its own tile rather than being taken from the page's generic row count — the generic count is replaced by an all-time snapshot on any page carrying a snapshot tile, and this one carries four (Open Backlog Value, Average Backlog Age, Stalled Over 48 Hours and Time to Completion).

Products

/admin/ecommerce/productAnalytics. Stock and price are state, not flow, so every tile on this screen is a snapshot — the timeframe selector moves only the charts.

Tile What it measures
Catalogue Size Every product row that is not soft-deleted
Purchasable Products with status on
Out of Stock Live products at inventoryQuantity = 0 — listed and unbuyable
Low Stock (1–5 Left) Live products with 1 to 5 units, strictly. A product at zero is counted by the tile to its left, never by both
Inventory Value on Hand SUM(price × inventoryQuantity) over purchasable products, converted to USD
Average price Mean price over purchasable products — no currency
Priced at Zero Live, purchasable products with price = 0. The product form accepts zero, so this is a real thing to check

Charts: Listings Created by Type (Selected Period), Average Price by Type, Inventory Value by Currency, New Listings by Type Over Time.

Only the first and last of those move with the timeframe; the two ranked bars describe the shelf as it stands.

There is no sales or velocity figure on this screen. Units sold live on the order-item table, which this page does not read. The store dashboard does read it, and that is where units sold and the top-products ranking are.

Discounts

/admin/ecommerce/discountAnalytics. A coupon estate is state, so every tile here is a snapshot.

Tile What it measures
Active Codes with status on
Redeemable Right Now status on and validUntil still in the future
Expired but Still Marked Live status on and validUntil already passed
Live Codes With No Usage Cap status on and maxUses empty — unbounded exposure
Fixed-Value Liability SUM(amount) over redeemable FIXED codes, converted to USD through the attached product's currency
Average Depth (Percentage Codes) Mean percentage over redeemable PERCENTAGE codes
Average Value (Fixed Codes) Mean amount over redeemable FIXED codes — no currency
Redeemable 100% Codes Redeemable PERCENTAGE codes at exactly 100 — codes that make an order free

Charts: Codes Issued by Type (Selected Period), Codes Issued by Type Over Time, Most-Issued Depths (% Off).

Every code on that tile looks usable in the discount table — its status badge says active — and fails at checkout, because checkout also requires validUntil >= now. The gap between Active and Redeemable Right Now is dead configuration for you to clear, and it is the reason a customer can report that "your code does not work" while the admin screen says it is on.

Two limits worth knowing:

  • Redeemable does not check the start date. A code with a validFrom in the future is counted as redeemable here. Only expiry is read.
  • Nothing on this screen counts redemptions. Usage against maxUses lives in a separate table the analytics engine cannot join to from here. Fixed-Value Liability is the value of codes outstanding, not of codes taken.

Shipping

/admin/ecommerce/shippingAnalytics.

Tile What it measures Scope
In transit Loads at loadStatus = TRANSIT Snapshot
Awaiting Dispatch Loads at PENDING Snapshot
Overdue Deliveries PENDING or TRANSIT with a deliveryDate already passed — the call list Snapshot
On-Time Delivery Rate Delivered On Time ÷ Deliveries With a Promised Date Window
Shipping Spend SUM(cost) over loads not CANCELLED Window
Shipping Tax SUM(tax) over loads not CANCELLED Window
Tonnage Delivered SUM(weight) over DELIVERED loads Window
Volume Delivered SUM(volume) over DELIVERED loads Window
Delivered On Time DELIVERED loads whose last write was on or before the promised date Window
Deliveries With a Promised Date DELIVERED loads that carried a deliveryDate at all Window
Average Transit Time (All Time) Mean hours from booking to last write, over DELIVERED loads Snapshot

Charts: Shipping Spend by Carrier, Late Deliveries by Carrier (both ranked on the free-text transporter field, top five plus "Other"), and Shipping Spend Over Time.

The shipment table has no currency column. cost and tax are whatever an operator typed off a carrier invoice, in whatever currency that invoice was in. Rendering them with a dollar sign would be a claim the data cannot support, so they are shown as plain numbers. If you run one carrier in one currency, the figures are that currency; if you do not, they are a sum of unlike things and you should read the per-carrier bars instead.

The on-time rate is measured only against loads that carried a promised date — a shipment with no deliveryDate is in neither the numerator nor the denominator. Both of those tiles are on the page so you can see how much of your traffic the rate actually covers. And, as with orders, there is no deliveredAt column: updatedAt stands in for the delivery write, so a shipment edited after it landed reads as late.

Cross-check Shipping Spend here against what the order desk billed customers for shipping. Shipping is sold at one flat store-wide fee (ecommerceDefaultShippingCost, on Store settings), so if carrier spend exceeds what you collected, every physical order is losing money.

Reviews

/admin/ecommerce/reviewAnalytics.

Tile What it measures Scope
Average Rating Mean rating over published reviews only Snapshot
Negative Rate (1–2★) Negative ÷ Published, over the window Window
Moderation Queue (Hidden) Reviews with status off — the queue Snapshot
Products With a Review Distinct products carrying at least one published review Snapshot
Published This Period Published reviews created in the window Window
Negative (1–2★) Published reviews rated 1 or 2 Window
Neutral (3★) Published reviews rated exactly 3 Window
Positive (4–5★) Published reviews rated 4 or 5 Window

Chart: Reviews Over Time, a stacked bar of the three bands. The bands stack to the period's published total, so the shape of the curve is readable without a second chart.

Average Rating counts published reviews only, deliberately: hiding a review in moderation must not be able to move the storefront's score, and a moderator should not be able to lift a product's rating by unpublishing its critics.

Negative Rate is a rate rather than a count because a spike in 1★ reviews during a spike in orders may be nothing at all.

Not available here: worst-rated products, and review coverage against catalogue size. Both need a join to the product table that this screen cannot make.

Wishlist

/admin/ecommerce/wishlistAnalytics. The smallest of the six, because the table it sits on is small — a wishlist row is a container with an id, an owner and a creation date, and everything commercially interesting (which product, how many people want it) lives in a child table this screen does not read.

Tile What it measures Scope
Total Wishlists Every wishlist container that exists Snapshot
Customers With a Wishlist Distinct owners Snapshot
Average Wishlist Age Mean hours since creation across the whole estate Snapshot
Opened This Period Containers created in the window Window
Opened Over a Year Ago Containers older than one year Snapshot

Chart: Wishlists Over Time.

Read Opened Over a Year Ago against Total Wishlists. If most of the estate is on that tile and Opened This Period is flat, the wishlist feature is an archive rather than a signal, and nothing else on the platform will tell you that.

There is no most-wishlisted-product ranking and no wishlist-to-purchase conversion figure. Ranking this table by customer would give every bar a value of 1, because it holds one container per customer.

Reading durations

Any tile formatted as a duration is computed in hours. Below one hour it prints minutes, up to 48 hours it prints hours, and past that it prints days. A "2.5d" backlog age is 60 hours.

When a tile shows an em dash

means the figure is unknown, not zero. Either the request failed, or that card's configuration is broken and the engine returned an error for it in place of a number. A genuine zero renders as 0. That distinction is deliberate — a confident 0 on a broken card is the harder failure to notice.

If the whole tab fails to load, check access.admin on the role first; that is the common cause.

Next: Orders and fulfilment for what the order states actually mean, or Where store money lands in the platform ledger when a revenue figure needs reconciling against the transaction table.