SaaS Metrics Dashboard: MRR, ARR and Churn From Your Own Data
A SaaS metrics dashboard should show six numbers: MRR, ARR, net new MRR, customer churn, net revenue retention and trial-to-paid conversion, plus one table of who your biggest customers are. Everything else is either a breakdown of those or a vanity metric. This page covers what each one tells you, the exact JSON each widget takes, and the SQL for the one people get wrong most often: churn.
If you want the revenue side in more depth, MRR movement and ARPA, our revenue dashboard guide goes further. Earlier than this? The startup metrics dashboard is the pre-product-market-fit version. This page is the wider SaaS picture.
The six metrics, and why each earns its tile
- MRR (monthly recurring revenue). The headline. Normalise annual plans to a monthly figure before summing, or a single annual deal makes one month look heroic.
- ARR (annual recurring revenue). MRR × 12. It's the same information, but investors and planning conversations use it, so it's worth a tile if you raise or report.
- Net new MRR. New + expansion − contraction − churned MRR this month. One number that says whether you're growing, and the clearest single trend line in SaaS.
- Customer churn rate. Customers lost this month ÷ customers at the start of the month. Keep it separate from revenue churn: losing ten small accounts and one large one are different problems.
- Net revenue retention (NRR). Revenue this month from last year's customers ÷ their revenue a year ago. Above 100% means existing customers grow faster than they leave.
- Trial-to-paid conversion. Trials that converted ÷ trials that ended. The earliest warning sign that onboarding or pricing broke.
A seventh tile, top customers by MRR, is worth adding at any size. It's the list you'll want the moment one of them goes quiet.
What to leave off
- Lifetime revenue. It can only go up, so it never tells you anything.
- Total signups. Same problem. Show signups for a period, or conversion, instead.
- LTV on day one. With little history, lifetime value is a guess dressed as a number. Add it when you have a couple of years of retention data.
- Twelve charts of the same thing. If two widgets would always move together, keep one.
The layout
Seven widgets fit one screen, on a laptop or an office TV:
| Widget | Type | Why this type |
|---|---|---|
| MRR | KPI with goal bar | One number against a target reads fastest |
| ARR | KPI | Same number, planning view |
| Net new MRR | Line chart | The trend matters more than any single month |
| Customer churn | KPI with alert | Should demand attention when it rises |
| NRR | Gauge | Reads instantly against the 100% line |
| Trial conversion | KPI | Early warning, one number |
| Top customers | Table | Names, plans, MRR |
The JSON, widget by widget
dashboardbase renders whatever your endpoints return, as long as it's in the widget contract. These are real responses; your endpoint fills in the values.
MRR with a goal bar:
{
"title": "MRR",
"actions": [
{
"title": "View Details",
"type": "link",
"url": "https://example.com/mrr-details"
}
],
"data": {
"header": {
"title": "$8,200",
"subtitle": "toward $10k goal",
"badge": {
"text": "+$820",
"icon": "ArrowUp",
"color": "Success"
}
},
"progress": {
"value": 8200,
"max": 10000,
"label": "Goal 10.000 $"
}
}
}Customer churn, flagged when it rises:
{
"title": "Churn rate",
"actions": [
{
"title": "View Cohorts",
"type": "link",
"url": "https://example.com/churn"
}
],
"data": {
"header": {
"title": "4.8%",
"subtitle": "vs last month",
"badge": {
"text": "+0.6%",
"icon": "ArrowUp",
"color": "Danger",
"fill": "Outline"
}
}
}
}Note the colours: a rising churn arrow is Danger, not Success. Up isn't always good, and the endpoint is where you decide that.
NRR as a gauge:
{
"title": "Net revenue retention",
"actions": [
{
"title": "View Status",
"type": "link",
"url": "https://example.com/nrr"
}
],
"data": {
"header": {
"title": "108%",
"subtitle": "Last 12 months"
},
"value": {
"value": 108,
"postfix": "%"
},
"maxValue": 150
}
}Top customers: a table where every cell is an object, so a cell can carry a link or a badge:
{
"title": "Top customers",
"actions": [
{
"title": "View All",
"type": "link",
"url": "https://example.com/customers"
}
],
"data": {
"columns": [
{
"text": "Customer",
"width": 45
},
{
"text": "Plan",
"width": 30
},
{
"text": "MRR",
"width": 25
}
],
"rows": [
[
{
"text": "Acme Corp",
"link": "https://example.com/customers/acme"
},
{
"text": "Enterprise",
"badge": {
"text": "Enterprise",
"color": "Dark"
}
},
{
"text": "$1,200"
}
]
]
}
}The churn endpoint
Churn is where definitions quietly drift, so compute it in your own code, where the rule is written down. This assumes a subscriptions table with customer_id, started_at and a nullable ended_at:
const SECRET = process.env.DASHBOARDBASE_ENDPOINT_SECRET
// Fail at boot rather than serving revenue data unprotected.
if (!SECRET) throw new Error('DASHBOARDBASE_ENDPOINT_SECRET is not set')
app.use('/dashboard', (req, res, next) => {
if (req.get('x-dashboardbase-secret') !== SECRET) {
return res.status(401).json({ error: 'unauthorized' })
}
next()
})
const CHURN_SQL = `
WITH bounds AS (
SELECT date_trunc('month', now()) - $1::interval AS start_at,
date_trunc('month', now()) - $1::interval + interval '1 month' AS end_at
)
SELECT
count(DISTINCT s.customer_id) FILTER (
WHERE s.started_at < b.start_at AND (s.ended_at IS NULL OR s.ended_at >= b.start_at)
) AS at_start,
count(DISTINCT s.customer_id) FILTER (
WHERE s.ended_at >= b.start_at AND s.ended_at < b.end_at
) AS churned
FROM subscriptions s, bounds b
`
async function churnRate(monthsAgo) {
const { rows } = await db.query(CHURN_SQL, [`${monthsAgo} months`])
const { at_start, churned } = rows[0]
return at_start > 0 ? (churned / at_start) * 100 : 0
}
app.get('/dashboard/churn', async (req, res) => {
const current = await churnRate(0)
const previous = await churnRate(1)
const diff = current - previous
res.json({
title: 'Churn rate',
actions: [{ title: 'View Cohorts', type: 'link', url: 'https://example.com/churn' }],
data: {
header: {
title: `${current.toFixed(1)}%`,
subtitle: 'vs last month',
badge: {
text: `${diff >= 0 ? '+' : ''}${diff.toFixed(1)}%`,
icon: diff >= 0 ? 'ArrowUp' : 'ArrowDown',
color: diff > 0 ? 'Danger' : 'Success',
fill: 'Outline',
},
},
},
})
})A customer who leaves and comes back in the same month counts as churned and as new. That's a choice; write it in a comment next to the query, so the next person reading the dashboard knows what the number means.
The x-dashboardbase-secret header is sent by dashboardbase on every request, so the endpoint rejects everyone else. When you test the datasource, dashboardbase also makes one request with every credential stripped and tells you whether the endpoint really refused it.
Getting the endpoints written
You don't have to write seven handlers by hand. The free Skill teaches Claude Code or Cursor the full contract, so "add an NRR gauge endpoint" comes back correctly shaped. Or let the editor generate a copy-pasteable prompt for whatever AI tool you use. Either way, check each response in the public endpoint validator before wiring it up.
If your subscription data lives in Stripe rather than your own database, the Stripe MRR dashboard tutorial shows the same idea with Stripe's API. For a mobile app on RevenueCat, see the mobile app KPI dashboard.
Where to look at it
- On your phone. Native iOS and Android apps, with a push notification when an endpoint raises an alert, such as churn crossing your threshold.
- On the office TV. Pair it by code at
www.dashboardbase.com/tv; nothing logs in on the screen. How TV pairing works. - With investors. A public share link, optionally password-protected.
When a subscription-analytics tool is the better call
Be honest about the trade-off. Tools like ChartMogul and Baremetrics connect to your billing system and compute these metrics for you, with cohorts and segmentation built in.
- ChartMogul is free up to $10K MRR; paid plans scale with ARR from $57/month (Starter) or $99/month (Pro), less 17% billed annually.*
- Baremetrics starts at $75/month for up to $360K ARR, scaling with revenue, and connects to Stripe, the App Store, Google Play and others.*
Pick one of those if you want churn cohorts and LTV curves without writing SQL, and your billing system is the source of truth. Build it from your own endpoints if your definitions are specific, your data spans several systems, or you want the numbers on phones and a TV alongside your product metrics, on one flat price.
FAQ
What should be on a SaaS metrics dashboard? MRR, ARR, net new MRR, customer churn, net revenue retention and trial-to-paid conversion, plus a top-customers table.
How do I calculate churn rate? Customers who cancelled during the month ÷ customers active at the start of the month. Keep revenue churn as a separate number.
Is there a free SaaS metrics dashboard? ChartMogul is free up to $10K MRR.* dashboardbase has a 14-day free trial, then flat plans from $9.99/month.
About the competitor details *
Competitor pricing changes, and we don't control it. Everything marked with an asterisk reflects the vendors' public pricing pages as of 6 October 2026. Check the vendor's current pricing page before you decide.
Sources
- ChartMogul pricing, checked 6 October 2026
- Baremetrics pricing, checked 6 October 2026