Metric · E-commerce

What is refund rate, and how do you measure it in Metabase?

Refund rate is the share of orders — or better, the share of revenue — that you end up giving back. It's the quality metric of a store: where conversion rate measures whether people buy, refund rate measures whether they keep what they bought. Measure it in Metabase from store and payment data synced into a database (Shopify, WooCommerce, or Stripe).

TL;DR — refund rate = refunded revenue ÷ revenue, attributed to the month the order was placed, not the month the refund landed. It's a lagging metric: recent months always look clean because their return window hasn't closed yet.

How is refund rate calculated?

Two variants, one attribution rule, one distinction:

  • Count-based — refunded orders ÷ total orders. Simple, but you must decide whether a partially refunded order counts as refunded.
  • Value-based — refunded amount ÷ revenue. Handles partial refunds naturally and weights refunds by what they actually cost you. Prefer this for the headline card.
  • Cohort attribution — assign each refund to the month its order was placed. A refund-month view mixes June's service with March's products and makes bad cohorts invisible.
  • Not chargebacks — chargebacks are involuntary, carry dispute fees, and have network-imposed thresholds. Track them as a separate metric next to failed payment rate.

What data does refund rate need?

  • An orders table with net_total, created_at, and a financial status.
  • A refunds table keyed by order_id with amount and its own timestamp — partial refunds mean one order can have several rows.
  • Refund line items (or at least a reason code) so you can segment by product, SKU, and cause.
  • Channel or campaign fields to find where refund-prone orders come from.

SQL patterns

Value-based refund rate by order cohortPostgreSQL

Refunded value joined back to the month each order was placed.

-- Value-based refund rate by the month the ORDER was placed,
-- not the month the refund landed.
WITH refunds_per_order AS (
  SELECT order_id, SUM(amount) AS refunded_amount
  FROM refunds
  GROUP BY order_id
)
SELECT
  date_trunc('month', o.created_at)               AS order_month,
  COUNT(*)                                        AS orders,
  SUM(o.net_total)                                AS revenue,
  SUM(COALESCE(r.refunded_amount, 0))             AS refunded,
  ROUND(100.0 * SUM(COALESCE(r.refunded_amount, 0))
        / NULLIF(SUM(o.net_total), 0), 2)         AS refund_rate_pct
FROM orders o
LEFT JOIN refunds_per_order r ON r.order_id = o.id
WHERE o.financial_status IN ('paid', 'partially_refunded', 'refunded')
GROUP BY date_trunc('month', o.created_at)
ORDER BY order_month;
Refund rate by productPostgreSQL

Rank products by share of value refunded over the last 180 days.

-- Products ranked by share of value refunded, with a volume
-- floor so one bad order doesn't top the list.
WITH refunds_per_line AS (
  -- Collapse the many partial-refund rows a line item can accrue into one
  -- total. Without this, the LEFT JOIN fans out and every extra refund row
  -- re-counts line_total, understating the rate.
  SELECT order_id, line_item_id, SUM(refunded_amount) AS refunded_amount
  FROM refund_line_items
  GROUP BY order_id, line_item_id
)
SELECT
  li.product_name,
  COUNT(DISTINCT li.order_id)                     AS orders,
  SUM(li.line_total)                              AS line_revenue,
  ROUND(100.0 * SUM(COALESCE(rl.refunded_amount, 0))
        / NULLIF(SUM(li.line_total), 0), 2)       AS refund_rate_pct
FROM order_line_items li
LEFT JOIN refunds_per_line rl
  ON rl.order_id = li.order_id
 AND rl.line_item_id = li.id
WHERE li.created_at >= CURRENT_DATE - INTERVAL '180 days'
GROUP BY li.product_name
HAVING COUNT(DISTINCT li.order_id) >= 50
ORDER BY refund_rate_pct DESC
LIMIT 20;

Pitfalls

Attributing refunds to the month they were issued.→ That charges June's chart for March's mistakes and lets a genuinely bad cohort hide. Join refunds to their orders and report by order month.
Reading recent cohorts as final.→ Refund rate lags by the length of your return window. Label the last one or two cohorts as immature, or exclude them from trend comparisons entirely.
Ignoring partial refunds in a count-based rate.→ Counting a $5 goodwill credit the same as a full return distorts both directions. Use a value-based rate, or set an explicit threshold for what counts as a refunded order.
Blending refunds and chargebacks into one number.→ They have different causes, different costs, and different consequences — a rising chargeback rate can end your card processing. Keep them on separate cards.

Where this metric applies

Analytics

Metrics

FAQ

Count-based or value-based refund rate?
Value-based (refunded revenue ÷ revenue) is usually the better default because it handles partial refunds naturally and weights a refunded $400 order more than a refunded $12 one. Count-based (refunded orders ÷ orders) is easier to explain and fine for operational monitoring — just decide how a partially refunded order counts before you ship the card. Pair either with average order value so a shift in basket size doesn't masquerade as a returns problem.
Should refunds count in the month they're issued?
Not for quality analysis. A refund issued in June against a March order says something about March's products and promises, so attribute it to the order's cohort month. Refund-issued-month views are still useful for cash and support staffing, but keep them on separate cards — and remember that recent cohorts always look artificially clean because their return window is still open.
What's a good refund rate?
It depends almost entirely on category — apparel with free returns lives at a rate that would be alarming for consumables. Benchmark against your own history by cohort, and watch segments rather than the blended number: a stable overall rate can hide one SKU or channel going bad. The product-level ranking on an e-commerce sales dashboard is where the actionable signal lives.
How is refund rate different from chargeback rate?
A refund is you giving money back voluntarily; a chargeback is the cardholder's bank clawing it back, with a dispute fee attached and a network-imposed ceiling that can threaten your merchant account. They have different causes too — refunds point at product and fulfillment, chargebacks at fraud and billing confusion. Track them separately, alongside failed payment rate on a payments health dashboard.
How do you track refund rate in Metabase?
Sync orders and refunds into a SQL database with a pipeline like Airbyte or Fivetran — from Shopify, WooCommerce, or Stripe — keeping refunds as their own table keyed by order_id. Join refunds back to orders, chart refunded value ÷ revenue by order month, and add product and reason-code breakdowns so a rising rate comes with a suspect list.