6 min read

How to Visualize ARR in Stripe

Your Stripe dashboard shows individual subscriptions, but not your total annual recurring revenue at a glance. ARR is the metric that actually matters for SaaS forecasting and board reporting. Here's how to extract it directly from Stripe and surface it where your team needs it.

Extract Active Subscriptions from Stripe

The foundation is querying all active subscriptions and pulling their pricing information. Stripe's API makes this straightforward.

List all active subscriptions via the API

Use the Subscriptions endpoint to fetch active subscriptions. You'll need your Secret Key (found in Settings > API keys in the Stripe dashboard). Filter by status: 'active' to exclude trials, paused, or canceled subscriptions.

javascript
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);

const subscriptions = await stripe.subscriptions.list({
  status: 'active',
  limit: 100,
});

subscriptions.data.forEach(sub => {
  console.log(`${sub.customer}: $${sub.items.data[0].price.unit_amount / 100} per ${sub.items.data[0].price.recurring.interval}`);
});
Lists all active subscriptions with customer ID and recurring price

Handle pagination for large subscription counts

The API returns up to 100 items by default. If you have more subscriptions, use the starting_after parameter with the has_more flag to fetch all pages. This is critical for accurate ARR calculations.

javascript
let allSubscriptions = [];
let hasMore = true;
let startingAfter = null;

while (hasMore) {
  const response = await stripe.subscriptions.list({
    status: 'active',
    limit: 100,
    starting_after: startingAfter,
  });
  allSubscriptions = allSubscriptions.concat(response.data);
  hasMore = response.has_more;
  if (hasMore) {
    startingAfter = response.data[response.data.length - 1].id;
  }
}

console.log(`Total subscriptions: ${allSubscriptions.length}`);
Iterates through paginated results until all subscriptions are fetched
Watch out: Stripe's test mode and live mode have separate subscription lists. Make sure you're querying the correct API key for the correct mode.

Calculate ARR from Subscription Pricing

Once you have subscriptions, normalize them to annual revenue. Different subscriptions use different billing intervals (monthly, yearly, custom), so you need to convert everything to an annual figure.

Convert all billing intervals to annual value

Stripe subscriptions are billed on different intervals. Multiply monthly amounts by 12, keep yearly amounts as-is. Skip one-time charges and focus only on recurring revenue items.

javascript
const subscriptions = await stripe.subscriptions.list({ status: 'active' });

let arr = 0;

subscriptions.data.forEach(sub => {
  sub.items.data.forEach(item => {
    if (!item.price.recurring) return; // skip one-time charges
    
    const amount = item.price.unit_amount / 100; // convert cents to dollars
    const interval = item.price.recurring.interval;
    
    if (interval === 'month') {
      arr += amount * 12;
    } else if (interval === 'year') {
      arr += amount;
    }
  });
});

console.log(`Gross ARR: $${arr.toFixed(2)}`);

Apply discounts and coupons to get net ARR

Many subscriptions have discounts applied via coupons. Check the discount field on each subscription and subtract both percentage and fixed-amount discounts. This gives you actual revenue, not list price.

javascript
let arr = 0;

subscriptions.data.forEach(sub => {
  let subAnnualValue = 0;
  
  sub.items.data.forEach(item => {
    if (!item.price.recurring) return;
    const amount = item.price.unit_amount / 100;
    const interval = item.price.recurring.interval;
    subAnnualValue += interval === 'month' ? amount * 12 : amount;
  });
  
  // Apply discount if present
  if (sub.discount && sub.discount.coupon) {
    if (sub.discount.coupon.amount_off) {
      subAnnualValue -= (sub.discount.coupon.amount_off / 100);
    } else if (sub.discount.coupon.percent_off) {
      subAnnualValue -= (subAnnualValue * sub.discount.coupon.percent_off / 100);
    }
  }
  
  arr += Math.max(subAnnualValue, 0); // prevent negative values
});

console.log(`Net ARR (after discounts): $${arr.toFixed(2)}`);
Tip: Store calculated ARR with a timestamp so you can track month-over-month growth. Run this calculation daily or weekly to build a historical record for trend analysis.

Expose ARR in Your Dashboard or Alerts

Now expose the calculated ARR to your team. Cache the result rather than querying Stripe on every page load—the API will rate-limit you if you calculate ARR too frequently.

Build a cached API endpoint for ARR

Create a simple endpoint that returns ARR and caches the result. Update it on a schedule (e.g., hourly via a cron job) rather than on every request. This keeps your Stripe API quota low and response times fast.

javascript
// Express.js example
let cachedArr = null;
let lastCalculated = null;

app.get('/api/metrics/arr', (req, res) => {
  res.json({ arr: cachedArr, lastUpdated: lastCalculated });
});

// Calculate ARR on a schedule (e.g., every hour)
setInterval(async () => {
  const subscriptions = await stripe.subscriptions.list({ status: 'active' });
  let arr = 0;
  
  subscriptions.data.forEach(sub => {
    sub.items.data.forEach(item => {
      if (!item.price.recurring) return;
      const amount = item.price.unit_amount / 100;
      arr += item.price.recurring.interval === 'month' ? amount * 12 : amount;
    });
  });
  
  cachedArr = arr;
  lastCalculated = new Date().toISOString();
  console.log(`ARR updated: $${arr.toFixed(2)}`);
}, 60 * 60 * 1000); // every hour

Export to a BI tool or data warehouse

For deeper analysis, export subscription data to Google Sheets, Looker, Metabase, or your data warehouse. Pull data on a schedule and compute ARR trends, cohort analysis, and churn impact alongside other metrics.

javascript
// Export subscriptions to structured format for BI tools
const subscriptions = await stripe.subscriptions.list({ status: 'active', limit: 100 });

const exportData = subscriptions.data.map(sub => ({
  subscription_id: sub.id,
  customer_id: sub.customer,
  plan_name: sub.items.data[0].price.nickname,
  amount_cents: sub.items.data[0].price.unit_amount,
  interval: sub.items.data[0].price.recurring.interval,
  status: sub.status,
  created_date: new Date(sub.created * 1000).toISOString(),
  discount_percent: sub.discount?.coupon?.percent_off || 0,
}));

// Send to your BI tool via webhook or database insert
console.log(JSON.stringify(exportData, null, 2));
Watch out: ARR calculated from subscriptions is gross ARR (before chargebacks, failed charges, or refunds). Track failed invoices separately via the Invoices endpoint if you need net realized revenue.

Common Pitfalls

  • Forgetting to filter by status: 'active'—trials, paused, and canceled subscriptions will inflate your ARR calculation.
  • Not normalizing billing intervals—mixing monthly and yearly subscriptions without converting to annual values produces meaningless numbers.
  • Ignoring applied discounts and coupons—your calculated ARR will be higher than actual revenue if you skip the discount calculation.
  • Querying Stripe on every page load—you'll hit rate limits. Cache the result and update it on a schedule instead.

Wrapping Up

You now have a repeatable way to calculate ARR directly from Stripe and expose it to your team. This gives you a real-time view of recurring revenue, critical for forecasting and investor reporting. If you want to track this automatically across Stripe and other payment tools, Product Analyst can help.

Track these metrics automatically

Product Analyst connects to your stack and surfaces the insights that matter.

Try Product Analyst — Free