Ask most Stripe or Paddle dashboards "how much of my MRR growth came from existing customers upgrading, versus new customers signing up," and you'll get a shrug. Neither platform stores an "upgrade" event. What they store is invoices - and turning a pile of invoices into a clean answer is a harder problem than it looks.
This post covers why that's true and how Chartsy solves it.
What Is Expansion MRR (and Why It's Hard to Calculate)
Expansion MRR is the additional recurring revenue existing customers add through upgrades, add-ons, or seat increases - as opposed to new MRR from new customers or reactivation MRR from returning ones. It's one of the clearest signals of product-market fit: a business with strong expansion MRR grows even if it stopped acquiring new customers tomorrow.
The problem: neither Stripe nor Paddle has a native "subscription changed from Plan A to Plan B" event you can query directly. What you get instead is a stream of invoices and subscription-status snapshots. Whether a given invoice represents a new subscription, an upgrade, a downgrade, a renewal, or a reactivation has to be inferred - by comparing what a customer was billed this period against what they were billed last period.
The Core Technical Problem
Here's what actually happens on the wire. A customer on a $49/month plan upgrades to $99/month on the 12th of a 30-day billing cycle. What lands in your data isn't an "upgrade" record - it's:
- A prorated credit for the unused portion of the $49 plan
- A prorated charge for the remaining days on the $99 plan
- A new line item on the subscription reflecting the new price, effective next cycle
To correctly classify this as $50/month of expansion MRR, you need to reconstruct the before and after recurring amount from a transaction record that's really describing a one-time proration event, not a clean before/after comparison.
Multiply that by pauses, quantity-based pricing (seats), coupons that expire mid-relationship, plan swaps combined with billing-interval changes (monthly to annual), and doing this correctly at scale - across both Stripe's and Paddle's different data models - is the actual engineering problem behind "just show me my MRR breakdown."
How Chartsy Classifies Each MRR Movement
Chartsy computes MRR movement by comparing each customer's normalized recurring amount in the current period against their normalized recurring amount in the prior period, using a windowed comparison per customer rather than per invoice line item. The classification logic works out to:
| Comparison | Classification |
|---|---|
| No subscription last period → active this period | New MRR |
| Active last period → higher normalized amount this period | Expansion MRR |
| Active last period → lower (but still active) amount this period | Contraction MRR |
| Active last period → canceled this period | Churned MRR |
| Canceled previously → active again this period | Reactivation MRR |
| Active last period → same amount this period | No movement (retained) |
The key step is the normalized recurring amount - not the raw invoice total. A prorated $32 charge for 20 days of a $49/month plan needs to be normalized back to "$49/month equivalent" before it's comparable to any other period. Getting this normalization wrong is the single most common reason two tools report different MRR movement for the same underlying data.
Handling Proration and Mid-Cycle Changes
Upgrades and downgrades rarely happen on the first day of a billing cycle. Chartsy normalizes every invoice line item to a monthly-equivalent amount based on the actual billing interval and the proportion of the period it covers, rather than treating each invoice as a flat, comparable data point. A mid-cycle upgrade produces a partial-period invoice on the old plan and a partial-period invoice on the new plan in the same cycle - both get normalized and attributed to the correct before/after comparison, so the expansion amount reflects the actual new recurring rate, not an artifact of when in the cycle the change happened.
Handling Billing-Interval Changes
A customer switching from a $49/month plan to a $470/year plan (equivalent to $39.17/month) is a downgrade in monthly-normalized terms, even though the sticker price of the annual plan is higher. Comparing raw invoice totals across different billing intervals without normalizing to a common monthly baseline is a common source of incorrectly classified MRR movement - Chartsy always compares on the normalized monthly-equivalent value, never the raw invoice amount.
Handling Stripe vs. Paddle Differences
Stripe and Paddle (and Paddle Classic) don't share a status vocabulary or a subscription-change model. A few of the differences Chartsy's unified schema has to reconcile:
- "Canceling" vs. "canceled": Stripe distinguishes a subscription scheduled to cancel at period end (still active, still billing) from one that has actually ended. Treating these the same overstates churn by counting still-paying customers as already gone.
- One-time payment quirks: Paddle Classic historically handled one-time charges differently from recurring subscriptions in ways that can bleed into MRR calculations if not explicitly filtered out.
- Invoice vs. transaction models: Stripe's invoice object and Paddle's transaction object don't map one-to-one, particularly around how each represents partial refunds and mid-cycle adjustments.
Unifying these into one comparable schema before the MRR-movement classification even runs is a prerequisite step, not an afterthought - if the underlying event doesn't mean the same thing across both platforms, no amount of clever classification logic downstream will fix it.
Why This Level of Detail Matters
A single blended "MRR went up 4% this month" number is enough to file a report. It's not enough to run a business. Knowing that MRR grew 4% but expansion was flat and the growth came entirely from new customers tells a founder something completely different than knowing existing customers are expanding - the first says "acquisition is working," the second says "the product is sticky enough that customers pay you more over time." Net Revenue Retention, one of the metrics investors scrutinize most closely, is calculated directly from this same expansion/contraction/churn breakdown - get the classification wrong and NRR is wrong too.
See Your Own MRR Breakdown
Chartsy applies this classification automatically to your connected Stripe or Paddle account. You can ask ChartsyAI:
- "Break down this month's MRR into new, expansion, contraction, and churned"
- "How much of my MRR growth came from existing customers vs new signups?"
- "Show me my expansion MRR trend for the last 6 months"
Connect your account and see your real MRR breakdown →
Frequently Asked Questions
Why doesn't Stripe show expansion and contraction MRR directly? Stripe stores invoices and subscription objects, not a log of "this customer upgraded from Plan A to Plan B." Expansion, contraction, and reactivation have to be derived by comparing a customer's normalized recurring amount period over period - Stripe's native dashboard doesn't do this comparison for you.
What's the difference between expansion MRR and new MRR? New MRR comes from customers who weren't paying you last period and are now. Expansion MRR comes from customers who were already paying you and are now paying more - through an upgrade, an add-on, or a seat increase. Conflating the two overstates how much of your growth is coming from your existing base.
How do you handle a customer who switches from monthly to annual billing? By comparing the monthly-normalized equivalent, not the raw invoice amount. A $470/year plan normalizes to about $39.17/month - if that's less than what the customer paid monthly before, it's classified as a downgrade even though the annual invoice total looks larger.
Does this affect Net Revenue Retention calculations? Yes, directly - NRR is calculated from the same expansion, contraction, and churn classification described here. If upgrades and downgrades aren't classified correctly, NRR will be too, which matters given how closely investors scrutinize that specific metric.
Does this work the same way for Paddle as Stripe? The underlying goal is the same, but Paddle (and Paddle Classic) use a different data model than Stripe, so Chartsy normalizes both into one unified schema before running the same classification logic - meaning MRR movement is comparable across providers, including for businesses using both at once.
Related: How Chartsy Calculates MRR · What Is Expansion MRR? · The MRR Waterfall Chart · What Is Net Revenue Retention (NRR)? · Combining Stripe and Paddle Data Into One Dashboard

Written by
Chartsy TeamThe Chartsy Team writes guides, product updates, and resources to help SaaS and eCommerce founders make sense of their metrics, without SQL or spreadsheets.
Chartsy