Your existing customers are your best growth engine. Expansion revenue—upsells, cross-sells, and increased usage from current customers—signals retention health and product stickiness. Stripe lets you track it natively if you structure your billing correctly.
Defining Expansion Revenue in Stripe
Expansion revenue is incremental MRR from customers you already have, separate from new customer acquisition. It comes in three flavors: tier upgrades, product add-ons, and consumption increases.
Recognize the three types of expansion
Upsells happen when a customer moves to a higher-tier plan. Cross-sells are new products or features added to their subscription. Usage-based expansion occurs when metered consumption increases month-to-month. Stripe handles all three through subscription updates, line items, and usage events.
// Example: Upsell by updating a subscription to a higher plan
const subscription = await stripe.subscriptions.update('sub_123', {
items: [
{
id: 'si_starter_plan',
price: 'price_premium_monthly', // upgraded from price_starter_monthly
},
],
});
console.log(`New MRR: $${subscription.amount_subtotal / 100}`);
// The difference between old and new amount is your expansion revenueStructure pricing to make expansion visible
Use Stripe's tiered pricing or usage-based billing so expansion shows up clearly in your data. Avoid one-time charges or flat fees—they hide growth signals. If you charge per seat or per feature, use separate line items so you can isolate expansion in your queries.
// Example: Creating a metered price for usage-based expansion
const price = await stripe.prices.create({
currency: 'usd',
recurring: {
interval: 'month',
usage_type: 'metered',
aggregate_usage: 'sum',
},
billing_scheme: 'tiered',
tiers_mode: 'graduated',
tiers: [
{ up_to: 100, unit_amount: 10 }, // First 100 units @ $0.10
{ up_to_inf: true, unit_amount: 8 }, // Above 100 @ $0.08
],
});
console.log(`Metered price created: ${price.id}`);
// Usage increases automatically calculate expansion revenue each cycleTag plans with expansion metadata
Add custom fields to your products in the Dashboard > Products section, or use the API, to label expansion sources. Tag upsells, cross-sells, and usage tiers separately so you can filter them later in reports.
// Example: Create a plan with expansion-tracking metadata
const product = await stripe.products.create({
name: 'Analytics Pro',
metadata: {
expansion_type: 'upsell',
previous_tier: 'starter',
tier_level: 'premium',
},
});
const price = await stripe.prices.create({
product: product.id,
unit_amount: 9900,
currency: 'usd',
recurring: { interval: 'month' },
});
console.log(`Plan ${product.id} tagged for expansion tracking`);
// Filter invoices by product metadata to isolate expansion in analyticsCalculating Expansion Revenue from Invoices
Once pricing is set, expansion revenue lives in your invoices and subscription updates. You'll identify it by comparing what a customer paid month-to-month—if it went up for the same subscription, that delta is expansion.
Compare invoices month-over-month
For each customer, fetch their paid invoices for consecutive months. If this month's total is higher than last month's, that difference is expansion. Use the status: 'paid' filter to ignore drafts, and iterate through the results to calculate deltas.
// Example: Calculate expansion revenue for one customer
const invoices = await stripe.invoices.list({
customer: 'cus_abc123',
status: 'paid',
limit: 24, // Last 24 months
});
let totalExpansion = 0;
for (let i = 0; i < invoices.data.length - 1; i++) {
const thisMonth = invoices.data[i].amount_paid;
const lastMonth = invoices.data[i + 1].amount_paid;
const delta = thisMonth - lastMonth;
if (delta > 0) {
totalExpansion += delta;
console.log(`Expansion in period: $${(delta / 100).toFixed(2)}`);
}
}
console.log(`Customer expansion (24mo): $${(totalExpansion / 100).toFixed(2)}`);
// This focuses purely on growth from existing customer billingUse Reporting API for bulk analysis
Querying invoices one customer at a time doesn't scale. Use Stripe's Reporting API to generate a scheduled report of all invoices by customer and period. Download the CSV and calculate expansion in bulk with a script or SQL.
// Example: Request an invoice summary report for expansion analysis
const report = await stripe.reportRuns.create({
report_type: 'invoices.summary.1',
parameters: {
interval_start: Math.floor(Date.now() / 1000) - 86400 * 90, // Last 90 days
interval_end: Math.floor(Date.now() / 1000),
},
});
console.log(`Report queued: ${report.id}`);
// Poll until complete
const finished = await stripe.reportRuns.retrieve(report.id);
if (finished.status === 'succeeded') {
console.log(`Download CSV: ${finished.result.url}`);
// Parse CSV by customer, calculate month-over-month deltas
}Filter by subscription_update to isolate upsells
Upsells show up as invoices with billing_reason: 'subscription_update'. These are plan changes, not recurring base charges. Combine with amount_paid > 0 to exclude downgrades and get pure upsell expansion.
// Example: Find all upsell-driven expansion invoices
const upsellInvoices = await stripe.invoices.list({
billing_reason: 'subscription_update',
limit: 100,
});
let upsellExpansion = 0;
upsellInvoices.data.forEach((invoice) => {
// Only count positive amounts (expansion, not downgrade refunds)
if (invoice.amount_paid > 0) {
upsellExpansion += invoice.amount_paid;
console.log(`Upsell from ${invoice.customer}: $${(invoice.amount_paid / 100).toFixed(2)}`);
}
});
console.log(`Total upsell-driven expansion: $${(upsellExpansion / 100).toFixed(2)}`);
// billing_reason='subscription_update' filters out regular renewal cyclesbilling_reason to isolate the growth.Common Pitfalls
- Counting new customer onboarding as expansion—expansion is from existing customers only. Use subscription creation date to filter out first invoices.
- Ignoring credits and refunds—when a customer upgrades, Stripe prorates a credit for unused time. Use
amount_paid(net) instead ofamount_due(gross). - Forgetting churn in net revenue retention math—expansion revenue is gross growth only. If a customer churns, that's separate; don't mix expansion with NRR calculations.
- Treating all metered usage increases as expansion—if you only charge per-unit, you need to baseline usage per customer over time to spot anomalies from true expansion.
Wrapping Up
Expansion revenue in Stripe is the MRR growth from your existing customer base—tracked through tier upgrades, add-ons, and usage increases. Structure your pricing with tiers or metered billing, tag products with metadata, and query invoices month-over-month to measure it accurately. If you want to track expansion revenue automatically across all your SaaS tools and turn it into product insights, Product Analyst can help.