Analytics

Build a Fixed-Cohort Retention Bridge: GRR, NRR and the Snapshot Contract

Reconcile account-level MRR endpoints, separate expansion from losses, and expose the definition choices that make retention dashboards disagree. Includes tested JavaScript and a reproducible synthetic fixture.

The revenue dashboard says the customer base is almost unchanged. Customer success says a quarter of the starting revenue disappeared. Both statements can be arithmetically correct. The problem is often that one numerator includes new accounts while another follows the original cohort. Before choosing a retention campaign, build a bridge that makes membership, units and missing evidence explicit.

Editorial disclosure: AI-assisted research, writing and implementation by Growthcraft Editorial. This is an original operational synthesis, not a client case study or a vendor-certified implementation. All examples are synthetic. Akshay's personal review is not claimed. The executable JavaScript was checked with Node.js 24 and uses no credentials, network or paid API.

Four takeaways for the next retention review

  • Freeze the starting account cohort before calculating retention. New or reactivated accounts that start at zero belong outside its numerator.
  • Calculate gross and net retention together. Expansion can offset losses in net retention without undoing those losses.
  • Two endpoint snapshots describe a net change, not every cancellation or reactivation inside the interval.
  • Agree the recurring-revenue contract before reconciling dashboards. A correct formula cannot repair incomplete exports or incompatible definitions.

Why definition control matters now

ChartMogul's GRR documentation, updated 11 September 2026, describes excluded movements and configurable treatment of accounts leaving a plan segment. That is a timely technical reason to review metric contracts, not evidence of keyword volume or a universal retention benchmark. ChartMogul GRR report documentation.

Stripe documents downloadable subscriber MRR snapshots and MRR-change reports, together with configurable discount and active-subscriber definitions. Its standard MRR definition also distinguishes recurring amounts from metered usage. Check the current definition and configuration for your actual account before comparing reports. Stripe Billing analytics documentation, accessed 21 September 2026.

The method below solves a narrow engineering task: reconstructing a fixed-starting-cohort retention bridge from normalised account endpoints. It does not reproduce every vendor's movement logic, predict churn or certify product-market fit. A definition-first bridge is useful because disagreements become inspectable: a reviewer can see whether the difference comes from membership, valuation, identity mapping or arithmetic.

Write the snapshot contract before writing the query

Define the reporting entity first. An account might own several subscriptions, seats or products. Aggregate those records to the chosen account identity before comparison. If an organisation changes billing IDs, maintain a reviewed mapping; otherwise an administrative migration can appear as one lost account plus a new one. Do not deduplicate distinct customer entities simply because their names are similar.

Record two precise snapshot timestamps, their timezone and an extraction cutoff. The observation interval is the distance between the endpoints; monthly-normalised recurring revenue remains the monetary unit even if the interval is a quarter. A three-month NRR is not a monthly rate. Keep the period label beside the result and do not automatically annualise it.

The valuation contract should state recurring-item eligibility, discounts, tax exclusions, treatment of trial or delinquent items, and any usage-based component. This article does not prescribe a financial accounting policy. Finance must approve the chosen operational definition. Cash collected, invoiced revenue, recognised revenue and MRR can all be valid measures, but substituting one for another creates an incoherent ratio.

Choose a reporting currency and explain conversion. One useful diagnostic uses a constant conversion policy so that exchange-rate changes do not masquerade as customer expansion; another reports actual translated values with a separate FX bridge. Do not mix those interpretations in one number. The calculator expects pre-normalised two-decimal currency amounts and performs no foreign-exchange conversion.

Freeze membership and distinguish zero from missing

In this convention, an account enters the retention cohort only if its starting MRR is positive. It stays in the denominator even if its ending value is zero. An account starting at zero is excluded from both revenue-retention rates. Its ending amount is retained as an outside-cohort diagnostic; the snapshots alone cannot establish whether that account is new, reactivated or reclassified.

A missing end record must not silently become zero. In a production export, it may mean cancellation, a delayed data load, a changed identity or a filtered record. The data owner must establish which interpretation is correct. Build a complete comparison table with explicit endpoint evidence before passing it into the calculator. The tool deliberately rejects blank amounts rather than making that decision.

Freeze segment membership at the start as well. If the review concerns a starting plan or acquisition segment, keep those accounts through the end even when they switch plans. Selecting only accounts still in that segment at the end creates a survivor view. A separate migration analysis can answer where they went without quietly changing the original denominator.

Derive the bridge at account grain

For each starting-cohort account, let s be starting MRR and e be ending MRR. Retained gross MRR is min(s,e): an account can retain no more of its starting amount than it originally had. If e is zero, classify s as snapshot churn. If e is positive but smaller than s, classify s minus e as contraction. If e exceeds s, classify the difference as expansion.

Summing across starting accounts gives S, E, G, C, D and X: starting MRR, ending MRR, retained gross, churn, contraction and expansion. The two conservation checks are S − C − D + X = E and S − C − D = G. Gross revenue retention is G/S; net revenue retention is E/S. Logo retention is the number of starting accounts with positive ending MRR divided by the number of starting accounts.

These are ratios, not statistical estimates with an automatically available confidence interval. The calculator displays percentages, but its internal ratio .625 means 62.5%. If NRR is 72.5% and GRR 62.5%, the gap is 10 percentage points. That gap reflects expansion relative to starting MRR in this convention; it is not evidence that an intervention produced a 10% uplift.

A worked example that exposes the wrong numerator

AccountStarting MRREnding MRREndpoint interpretation
A100 EUR140 EUR40 expansion
B200 EUR150 EUR50 contraction
C100 EUR0 EUR100 churn
D0 EUR90 EUROutside starting cohort

The starting cohort is A, B and C. Starting MRR is 400, ending cohort MRR 290 and retained gross MRR 250. The bridge is 400 − 100 − 50 + 40 = 290. GRR is 62.5%; NRR is 72.5%. Two of the three starting accounts remain positive, giving logo retention of two-thirds, displayed as 66.67%.

Total ending MRR across every account is 380, including D. Dividing 380 by 400 gives 95%, a useful description of overall base size relative to the starting total but the wrong numerator for this retention question. New revenue can help the company grow while the starting cohort shrinks. Keep both facts visible rather than asking one headline to answer two different questions.

Executable JavaScript — exact endpoint aggregation

The following dependency-free example accepts integer minor units: 100 units equal one currency unit. That avoids repeated floating-point additions of decimal currency amounts. Each row must already represent one complete, normalised account. The bounds of 1,000 rows and 100 billion minor units per endpoint keep every aggregate within JavaScript's safe integer range. This is an analytical reducer, not a billing engine.

function snapshotRetention(rows) {
  if (!Array.isArray(rows) || rows.length < 1 || rows.length > 1000) throw new Error("Supply 1–1,000 rows.");
  const ids = new Set();
  let start = 0, end = 0, gross = 0, churn = 0, contraction = 0, expansion = 0, outside = 0, logos = 0, retainedLogos = 0;
  for (const row of rows) {
    if (!row || typeof row.id !== "string" || !/^[A-Za-z0-9_-]{1,64}$/.test(row.id) || ids.has(row.id)) throw new Error("Invalid or duplicate account ID");
    ids.add(row.id);
    for (const value of [row.startMinor, row.endMinor]) if (!Number.isSafeInteger(value) || value < 0 || value > 1e11) throw new Error("Invalid minor-unit MRR.");
    const s = row.startMinor, e = row.endMinor;
    if (s === 0) { outside += e; continue; }
    logos++; start += s; end += e; gross += Math.min(s, e);
    if (e === 0) churn += s;
    else { retainedLogos++; contraction += Math.max(0, s - e); }
    expansion += Math.max(0, e - s);
  }
  return { start, end, gross, churn, contraction, expansion, outside, logos, retainedLogos,
    grr: start === 0 ? null : gross / start,
    nrr: start === 0 ? null : end / start,
    logoRetention: logos === 0 ? null : retainedLogos / logos };
}
const example = snapshotRetention([
  { id: "A", startMinor: 10000, endMinor: 14000 },
  { id: "B", startMinor: 20000, endMinor: 15000 },
  { id: "C", startMinor: 10000, endMinor: 0 },
  { id: "D", startMinor: 0, endMinor: 9000 }
]);
console.log(example);
// grr: 0.625; nrr: 0.725; end: 29000; outside: 9000

Save the code as a .mjs file and run it with Node.js 24. Duplicate IDs are rejected, even if the amounts are identical, because a comparison table is expected to have exactly one row per account. If your ingestion layer legitimately repeats facts, resolve them there with an auditable identity policy. Silently picking one row can conceal conflicting snapshots.

The browser calculator parses a deliberately small CSV format with three columns and no quoted fields. It rejects negative values, blanks, exponents and more than two decimal places. IDs are restricted to anonymised letters, numbers, dashes and underscores. The exported JSON contains aggregate results and the scope description, not account rows. Keep the scope description free of personal data too.

Test the contract, not only the happy-path result

Start with the worked fixture and assert every bridge component. Then test an all-zero starting base: retention must be undefined, not zero or 100%. Test complete churn, unchanged accounts, pure expansion and outside-only growth. Expansion should allow NRR above 100%, while GRR remains at most 100%. Increasing outside-cohort MRR must not change either retention ratio.

Property checks are particularly useful here. Reordering rows should not change the result. Scaling all monetary amounts by the same positive factor should preserve ratios within numerical tolerance. For every valid fixture, the two bridge identities should hold exactly in minor units, GRR should be between zero and one, and NRR should be at least GRR. Test the maximum permitted row count and amount before trusting the aggregate precision.

Also test what the UI promises: editing an input must clear stale results; invalid input must expose a readable error; keyboard submission must reach the result; reset must restore the synthetic fixture; and copied or downloaded output must match the displayed calculation. Those checks protect the decision workflow, not just the underlying formula.

Know when endpoint retention is the wrong question

Consider an account starting at 100, rising to 200 and ending at 150. The endpoint method records 50 expansion and no contraction. A movement report may expose both a 100 expansion and a later 50 contraction. Neither representation should be silently relabelled as the other. If your decision concerns downgrade events, use the movement ledger; if it concerns what the starting cohort is worth at the endpoint, the snapshot bridge is directly useful.

Likewise, an account can cancel and reactivate between snapshots while ending at its original MRR. The bridge records full endpoint retention but cannot establish uninterrupted service. A cohort that includes an annual renewal also has different exposure from a cohort with no renewal in the interval. Segment by appropriate lifecycle context before interpreting a comparison as performance.

Revenue concentration creates another limitation. A few large expansions can support NRR while many smaller accounts exit. Review logo retention and the distribution of account changes, not just the total. The small calculator does not estimate lifetime value, renewal probability, margin, price elasticity or campaign effects. Those require additional models and evidence.

Turn the bridge into an owned investigation

A balanced bridge establishes what changed under the chosen definition. It does not establish why. Ask customer success for verified cancellation and downgrade context, ask product for relevant usage evidence, and ask finance to reconcile price and currency changes. Keep explanations as hypotheses until supported. Do not automatically discount accounts because a retention percentage declined.

Archive the input definition, extraction cutoff, code version, aggregate output and reviewer decision together. When late data changes a snapshot, create a restated version rather than overwriting the previous conclusion without explanation. The useful governance question is whether the change would alter the action, not whether the dashboard can be made to look stable.

For an intervention, specify an accountable owner, a review date, economic and customer-quality guardrails, and an evaluation design suited to the question. A source reconciliation gap should hold the decision if it could reverse the conclusion. This method supplies no universal materiality percentage or retention benchmark. The approval belongs to the business, not to the calculator or an LLM-generated memo.

Use the connected working set

For the broader data-contract discipline, read marketing data contracts. For a different question—whether spending caused profitable growth—use the incrementality budget review. Retention accounting and causal evaluation are complementary, not interchangeable.

View all growth marketing articles