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).
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_idwithamountand 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
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;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
Where this metric applies
- Shopify + Metabase — orders, refunds, and refund line items with reasons
- WooCommerce + Metabase — order and refund records in WordPress
- Stripe + Metabase — refund objects tied to charges and disputes
- Square + Metabase — refunds across in-person and online orders
Related
Analytics
Metrics
FAQ
Count-based or value-based refund rate?
Should refunds count in the month they're issued?
What's a good refund rate?
How is refund rate different from chargeback rate?
How do you track refund rate in Metabase?
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.