Guides
Five days of VLOOKUP gymnastics to explain one number. The conditional logic that buckets every dollar of movement automatically, and the three places it breaks.

Most SaaS finance teams live in a state of spreadsheet hell for the first five days of every month. The CEO asks why MRR grew by $50,000, and the response is usually a frantic scramble to reconcile Stripe exports with a master Excel sheet. You know the total changed, but explaining exactly why, splitting that $50k into new logos, upsells, and returning customers, requires a level of manual VLOOKUP gymnastics that is prone to error and impossible to scale.
To move away from manual reconciliations, you need a programmatic way to bucket revenue changes. That starts with moving beyond total balances and adopting a movement-based logic that categorizes every dollar of change the moment it happens.
Key Takeaways
A revenue bridge is an arithmetic claim. Every dollar between last month's total and this month's has a named cause, and the bridge is where you make that claim.
Five movements carry the whole bridge. New, expansion, contraction, churn, and reactivation account for any change between two periods.
Classification is a rules problem, not a modeling problem. Compare each customer's recurring amount across two periods and the movement follows from the comparison.
Manual SQL and spreadsheets fail on repeatability, not arithmetic. A query rewritten each month is a fresh opportunity for the definition to drift without anyone noticing.
Refusing to guess is what makes the output worth believing. Genuinely ambiguous cases should be flagged for a person, not resolved quietly to keep the bridge tidy.
The Five Critical Movements of Recurring Revenue
To build an automated revenue bridge, you first need a standardized taxonomy. In SaaS accounting, there are five mutually exclusive movements that explain the delta between your beginning MRR and your ending MRR. The metrics those movements feed - MRR, ARR, net revenue retention - are defined in the metrics guide; this page is about how a single row gets assigned to one of the five.
The five movements of recurring revenue are the standardized categories used to classify how and why a customer’s monthly recurring revenue changed between two periods:
New business. Revenue from a customer who has never paid your company before.
Expansion. An increase in revenue from an existing customer, through seat adds, tier upgrades, or add-ons.
Contraction. A decrease in revenue from an existing customer who remains active but has downgraded their plan or reduced seat counts.
Churn. The total loss of revenue from a customer who has cancelled their subscription or failed to renew.
Reactivation. Revenue from a customer who had previously churned to zero but has returned to paying status.
Establishing these definitions is the first step toward automation. Most teams find expansion and churn easy to grasp, but reactivation is the most frequent point of failure in manual setups. Without a historical database check, returning customers get mislabelled as new business, which artificially flatters your customer acquisition efficiency and hides how much of your growth is actually a win-back loop.
A sixth category is worth naming even though it moves no money: the customers who paid exactly the same as last period. A taxonomy that only counts change treats them as absence, and then you cannot tell a stable base from a base nobody looked at.
The Logic: How to Automate Movement Classification
To automate these classifications, your system compares the state of a customer ID in month N against its state in month N-1. That takes more than a snapshot. It takes lookback logic to distinguish a brand-new customer from one returning from the dead.
How do you automatically categorize recurring revenue into expansion and churn? Apply a conditional framework that compares the current period to the previous period for every unique customer ID. If the ID exists in both periods and the value in N is greater than N-1, the difference is expansion. If it is lower but still above zero, it is contraction. If the ID existed in N-1 but has no balance in N, it is churn. Every dollar of movement then has a specific cause, which is what lets a revenue bridge reconcile the net change without manual intervention.
Implemented programmatically, in SQL or a data engine, the flow is:
Check for existence. If the ID is in month N and not in month N-1, it is either new or reactivation. To tell them apart, check every month prior to N-1. If the ID has ever carried revenue above zero, it is reactivation. If this is its first appearance anywhere, it is new.
Check for delta. If the ID is in both months, calculate the difference. Above zero is expansion. Below zero is contraction. Exactly zero is unchanged.
Check for absence. If the ID was in month N-1 and is not in month N, it is churn.
-- Classify every customer's MRR movement between two periods.
-- Telling new business from reactivation needs the FULL history,
-- not just the prior period.
with cur as (
select customer_id, sum(mrr) as mrr
from subscription_mrr
where period = date '2026-06-01'
group by customer_id
),
prev as (
select customer_id, sum(mrr) as mrr
from subscription_mrr
where period = date '2026-05-01'
group by customer_id
),
returning_customer as (
select distinct customer_id
from subscription_mrr
where period < date '2026-05-01'
and mrr > 0
)
select
coalesce(c.customer_id, p.customer_id) as customer_id,
coalesce(c.mrr, 0) - coalesce(p.mrr, 0) as delta,
case
when coalesce(p.mrr, 0) = 0 and r.customer_id is not null then 'reactivation'
when coalesce(p.mrr, 0) = 0 then 'new'
when coalesce(c.mrr, 0) = 0 then 'churn'
when c.mrr > p.mrr then 'expansion'
when c.mrr < p.mrr then 'contraction'
else 'unchanged'
end as movement
from cur c
full outer join prev p on p.customer_id = c.customer_id
left join returning_customer r
on r.customer_id = coalesce(c.customer_id, p.customer_id);
Applied across every customer record, this produces a bridge report. Your starting MRR plus the sum of the buckets equals your ending MRR, every time. That identity is the audit trail: if the two sides disagree, a category is missing or double-counted, and the gap tells you by exactly how much.
Why Manual SQL and Spreadsheets Fail at Scale
The logic above reads as straightforward. Implementing it in Excel or in ad-hoc SQL is where the reconciliation gap opens up, the point where the numbers in your dashboard refuse to tie out with your bank account or your accounting software.
Manual systems tend to fail on three specific complexities:
The mid-month timing trap. Most manual spreadsheets read month-end snapshots. If a customer upgrades on the 10th and downgrades on the 25th, the snapshot only sees the final result. The expansion and the contraction in between both vanish, and you get ghost figures that do not reflect what the customer actually did.
Partial refunds and credits. Billing exports often lump refunds into one general revenue bucket. In a movement bridge a refund is not necessarily contraction, it might be a one-time adjustment. If the logic cannot separate a recurring price change from a one-time credit, churn and contraction stay permanently skewed.
Currency and proration. For companies billing in multiple currencies, exchange rate movement can look like expansion or contraction even when the customer changed nothing. A spreadsheet that sums a mixed-currency column has produced a number that does not mean anything, and it will not tell you that it did.
These are the errors that produce hidden churn, where retention looks healthy because expansion from a few large accounts is masking a high volume of small cancellations.
Automating the Bridge with Morevy
If you are paying the SQL tax, the hours finance or data ops spend writing and debugging queries just to see last month’s performance, there is a shorter path.
Morevy runs this categorization natively. You give it your billing export and it classifies every movement into its own leg. The leg names differ from the accounting ones on purpose, because they describe what a billing export can actually prove:
New is new business
Returned is reactivation
Growth is expansion, meaning a customer paid more than last period
Contraction keeps its accounting name, meaning a customer paid less
Lost is churn
Unchanged is the sixth leg most taxonomies drop, the customers who paid the same as last period
The distance between the two vocabularies is the honest part. Growth means a customer paid more, which is not the same as an upgrade, and Lost means the payments stopped, which is not the same as a cancellation. An export can prove the first in each pair and not the second, so the leg is named for the thing it can prove.
The tie-out is enforced rather than presented. Starting plus New plus Returned plus Growth plus Contraction plus Unchanged plus Lost has to equal Ending, and every one of those terms is in the arithmetic. Suppress any of them and the close fails to tie by exactly the suppressed amount, which is the point: a bridge that can silently drop a term is not a bridge.
Messy data is surfaced rather than smoothed. A mixed-currency column does not get quietly converted at some assumed rate, because mixed currencies cannot be summed and a tool that pretends otherwise has invented a number. It comes back as a question to answer before the close runs. Same for an amount basis that looks net where the history was gross, or a date column where the day and month are ambiguous. Each real finding is a decision to make, not a footnote to discover later.
By automating the classification and surfacing only the genuine judgment calls, finance teams stop being data gatherers. Instead of spending a week working out what happened, you spend the time on why expansion is up, or how to prevent the churn the bridge just put in front of you.
Conclusion
A revenue bridge is not a chart. It is an arithmetic claim: that every dollar between last month’s total and this month’s has a named cause. Automating the classification is what makes that claim cheap to produce. Refusing to guess on the cases that are genuinely ambiguous is what makes it worth believing. Classification you cannot explain is only a faster way to be wrong, which is the difference between a number that is accurate and one that is defensible.
Related documentation: The three questions and When Morevy stops to ask.
What are the five movements of recurring revenue?
+
New business, expansion, contraction, churn and reactivation. They are the mutually exclusive categories that explain the difference between beginning MRR and ending MRR. A sixth category, the customers whose revenue did not change, moves no money but is worth tracking so you can tell a stable base from one nobody examined.
How do you automatically categorize recurring revenue into expansion and churn?
+
Compare each unique customer ID in the current period against the previous one. If the ID appears in both and the value rose, the difference is expansion. If it fell but stayed above zero, it is contraction. If the ID was present last period and has no balance this period, it is churn. If it is present now and absent before, check the full history to decide between new business and reactivation.
Why does reactivation get misclassified as new business?
+
Because distinguishing them requires checking every prior period, not just the one immediately before. A customer who churned eight months ago and came back looks identical to a first-time customer if your logic only looks one month back. The effect is that win-backs get counted as new logos, which flatters acquisition efficiency and hides how much growth is actually recovery.
Why do spreadsheet revenue bridges fail to tie out?
+
Three causes dominate. Month-end snapshots miss movements that happened and reversed inside the period. Refunds and one-time credits get lumped in with recurring changes, skewing churn and contraction. And mixed-currency columns get summed, which produces a number that does not mean anything.
What makes a revenue bridge auditable?
+
The identity has to be enforced, not presented. Starting revenue plus every movement leg must equal ending revenue, with no term omitted. If a bridge can silently drop a category, the total can still look right while the explanation underneath it is wrong, and nothing in the output tells you.
Built by and with finance teams
Better decisions start with better context
Join Waitlist