Expansion revenue—money from existing customers upgrading, adding seats, or moving to higher tiers—is often scattered across your subscription data. Stripe tracks every change, but pulling expansion metrics requires querying subscription history and comparing billing amounts. We'll show you how to extract this data and build a clear picture of your growth from within.
Extract Expansion Events from Stripe
Start by capturing subscription changes. You'll compare previous and current billing amounts to identify expansion.
Fetch active subscriptions and their current billing amounts
Use the Subscriptions API to list all active subscriptions. Extract the current unit_amount from each subscription's line items—this is your baseline.
const stripe = require('stripe')('sk_test_...');
const subscriptions = await stripe.subscriptions.list({
limit: 100,
status: 'active'
});
subscriptions.data.forEach(sub => {
const currentAmount = sub.items.data[0].price.unit_amount / 100; // Convert cents to dollars
console.log(`Subscription ${sub.id}: $${currentAmount}/month`);
});Listen for subscription updates via webhooks
Instead of polling, listen for customer.subscription.updated events. Stripe includes previous_attributes in the event payload, so you can immediately detect when a billing amount increased and calculate the expansion amount.
const handleWebhook = (event) => {
if (event.type === 'customer.subscription.updated') {
const current = event.data.object;
const previous = event.data.previous_attributes;
if (previous.items) {
const oldAmount = previous.items[0].price?.unit_amount || 0;
const newAmount = current.items.data[0].price.unit_amount;
if (newAmount > oldAmount) {
const expansion = (newAmount - oldAmount) / 100;
console.log(`Expansion detected: +$${expansion}`);
// Store in your database for later aggregation
}
}
}
};Distinguish expansion from new logo and churn
Only count increases from existing subscriptions as expansion. Use the subscription's customer ID and created timestamp to exclude brand-new subscriptions (new logo) and canceled ones (churn). Filter for subscriptions older than 30 days to avoid counting initial onboarding fee increases.
const thirtyDaysAgo = Math.floor(Date.now() / 1000) - (30 * 24 * 60 * 60);
const qualifyingSubscriptions = subscriptions.data.filter(sub => {
return sub.created < thirtyDaysAgo && sub.status === 'active';
});
// Now compare billing amounts for these subscriptions to prior month
const expansionRevenue = qualifyingSubscriptions.reduce((total, sub) => {
const currentAmount = sub.items.data[0].price.unit_amount / 100;
const priorAmount = getPriorMonthAmount(sub.id); // Fetch from your records
const expansion = Math.max(0, currentAmount - priorAmount);
return total + expansion;
}, 0);
console.log(`Expansion revenue: $${expansionRevenue}`);previous_attributes only includes fields that changed. If quantity increased but price stayed flat, check the quantity field instead of unit_amount. Also, prorated charges appear on invoices, not subscriptions, so query invoices if you need exact dollar amounts customers were billed.Calculate Expansion Metrics
Turn raw expansion amounts into actionable metrics that show growth trends.
Group expansion by month
Aggregate expansion events by calendar month or billing cycle. Store this in your database or a simple JSON structure so you can trend it over time.
// Group expansion events by month
const expansionByMonth = {};
expansionEvents.forEach(event => {
const month = new Date(event.timestamp * 1000).toISOString().split('T')[0].substring(0, 7); // YYYY-MM
if (!expansionByMonth[month]) {
expansionByMonth[month] = 0;
}
expansionByMonth[month] += event.expansion_amount; // Already in dollars
});
console.log(expansionByMonth);
// Output: { '2025-01': 3200, '2025-02': 4500, '2025-03': 5100 }Calculate expansion rate (expansion MRR ÷ prior month MRR)
Expansion rate shows what percentage of last month's recurring revenue came from existing customers. Divide this month's expansion by last month's total MRR. A 10% expansion rate means you added $10 of recurring revenue for every $100 of existing MRR.
function calculateExpansionRate(currentMonthExpansion, priorMonthMRR) {
return (currentMonthExpansion / priorMonthMRR) * 100;
}
const march2025Expansion = 5100; // From aggregation above
const february2025MRR = 48000; // $48k MRR at start of March
const rate = calculateExpansionRate(march2025Expansion, february2025MRR);
console.log(`March expansion rate: ${rate.toFixed(2)}%`); // Output: 10.63%Visualize Expansion Over Time
Build a simple chart to spot trends and communicate growth to stakeholders.
Format data for charting
Convert your monthly expansion data into an array that a charting library can consume. Include the month label and the dollar amount.
const chartData = Object.entries(expansionByMonth).map(([month, amount]) => ({
month,
expansion: amount,
formattedAmount: `$${(amount / 1000).toFixed(1)}k` // For display
}));
console.log(chartData);
// Output: [
// { month: '2025-01', expansion: 3200, formattedAmount: '$3.2k' },
// { month: '2025-02', expansion: 4500, formattedAmount: '$4.5k' },
// { month: '2025-03', expansion: 5100, formattedAmount: '$5.1k' }
// ]Render a bar or line chart
Use Chart.js, Recharts, or any charting library to display expansion revenue as a bar or line chart. A bar chart is clearer for month-over-month comparisons; a line chart is better for spotting trends.
// Example with Chart.js
const ctx = document.getElementById('expansionChart').getContext('2d');
const chart = new Chart(ctx, {
type: 'bar',
data: {
labels: chartData.map(d => d.month),
datasets: [{
label: 'Expansion Revenue (USD)',
data: chartData.map(d => d.expansion),
backgroundColor: '#171717',
borderRadius: 4
}]
},
options: {
responsive: true,
plugins: {
title: { display: true, text: 'Monthly Expansion Revenue' },
legend: { display: false }
},
scales: {
y: { beginAtZero: true }
}
}
});
module.exports = chart;Common Pitfalls
- Confusing expansion revenue with upsell or net revenue. Expansion is only from existing customers increasing their spend—don't include new logo revenue or revenue from add-on features sold separately.
- Forgetting that Stripe timestamps are Unix epoch in seconds, not milliseconds. Convert with
new Date(event.created * 1000)before grouping by date. - Not filtering out one-time charges or prorated amounts. Subscription
unit_amountis the recurring monthly charge; one-time setup fees or mid-cycle prorations live on invoices, not subscriptions. - Double-counting downgrades. Filter for increases only (
newAmount > oldAmount) so a customer dropping from $500 to $300 doesn't offset another customer's upgrade.
Wrapping Up
Expansion revenue is your fastest-growing, most profitable revenue stream—it costs less to land than new customers and signals product stickiness. By pulling subscription changes from Stripe, calculating monthly expansion metrics, and visualizing the trend, you'll identify which customer segments and niches are expanding fastest. If you want to track this automatically across tools, Product Analyst can help.