{"id":21216,"date":"2026-10-05T14:51:21","date_gmt":"2026-10-05T12:51:21","guid":{"rendered":"https:\/\/www.lukaswojcik.com\/blog\/?page_id=21216"},"modified":"2026-10-05T15:14:23","modified_gmt":"2026-10-05T13:14:23","slug":"mrr-churn-simulator","status":"publish","type":"page","link":"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/mrr-churn-simulator\/","title":{"rendered":"MRR and Churn Simulator"},"content":{"rendered":"<style>\n.lw-saas{color:var(--text-primary);min-width:0}\n.lw-saas *{box-sizing:border-box}\n.lw-saas h2,.lw-saas h3,.profile-panel:has(.lw-saas) .profile-name{overflow-wrap:anywhere;hyphens:auto}\n.lw-saas .saas-note{padding:16px;border-left:3px solid var(--accent);background:var(--bg-panel);color:var(--text-secondary)}\n.lw-saas fieldset{border:1px solid var(--border);border-radius:10px;padding:18px;margin:20px 0;min-width:0;background:var(--bg-panel)}\n.lw-saas legend{padding:0 8px;font-weight:700;color:var(--text-primary)}\n.lw-saas .saas-fields{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:16px}\n.lw-saas label{display:block;font-size:.9rem;color:var(--text-secondary)}\n.lw-saas input{display:block;width:100%;padding:11px;margin-top:6px;border:1px solid var(--border);border-radius:6px;background:var(--bg-body);color:var(--text-primary);font:inherit}\n.lw-saas input:focus-visible,.lw-saas button:focus-visible,.lw-saas a:focus-visible{outline:3px solid var(--accent);outline-offset:3px}\n.lw-saas input[aria-invalid=true]{border:2px solid var(--accent)}\n.lw-saas .saas-actions{display:flex;flex-wrap:wrap;gap:10px;margin:18px 0}\n.lw-saas button:disabled{opacity:.45;cursor:default;transform:none}\n.lw-saas [data-status]{color:var(--text-primary);font-weight:600}\n.lw-saas .saas-metrics{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px}\n.lw-saas .saas-metrics div{padding:16px;border:1px solid var(--border);border-radius:8px;background:var(--bg-panel)}\n.lw-saas dt{color:var(--text-secondary);font-size:.85rem}\n.lw-saas dd{margin:8px 0 0;font-size:1.35rem;font-weight:700;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}\n.lw-saas .saas-table-wrap{max-width:100%;overflow-x:auto;margin:20px 0;border:1px solid var(--border);border-radius:8px}\n.lw-saas table{border-collapse:collapse;width:100%;font-size:.86rem}\n.lw-saas table th,.lw-saas table td{width:auto!important;padding:12px;min-width:120px;word-break:normal;overflow-wrap:normal;text-align:right;border-bottom:1px solid var(--border);color:var(--text-primary);white-space:normal}\n.lw-saas table th{background:var(--bg-panel)}\n.lw-saas table th[scope=row],.lw-saas table thead th:first-child{position:sticky;left:0;z-index:1;border-right:1px solid var(--border)}\n.lw-saas caption{text-align:left;padding:12px;font-weight:700;color:var(--text-primary)}\n.lw-saas .saas-chart{margin:20px 0;color:var(--accent)}\n.lw-saas .saas-chart svg{display:block;width:100%;height:auto}\n.lw-saas figcaption{color:var(--text-secondary);font-size:.85rem}\n.lw-saas .saas-related{display:flex;gap:10px;flex-wrap:wrap;list-style:none;padding:0}\n.lw-saas .saas-related li{list-style:none;margin:0}\n.lw-saas .saas-related a{display:block;border:1px solid var(--border);border-radius:6px;padding:8px 12px}\n.site-container main:has(.lw-saas) .profile-panel{flex:1 1 0;max-width:none;min-width:0}\n.site-container main:has(.lw-saas) .info-panel{flex:0 0 220px;max-width:220px}\n@media(max-width:900px){.site-container main:has(.lw-saas) .profile-panel,.site-container main:has(.lw-saas) .info-panel{flex:auto;width:100%;max-width:100%}}\n@media(max-width:540px){.lw-saas .saas-fields,.lw-saas .saas-metrics{grid-template-columns:1fr}.lw-saas fieldset{padding:12px}}\n<\/style>\n<p><script>\/* SaaS calculators: pure arithmetic. Monetary inputs use one currency, exclude tax. *\/\n(function (scope) {\n'use strict';\nconst finite = x => typeof x === 'number' && Number.isFinite(x);\nfunction number(raw) {\n  const s=String(raw ?? '').trim().replace(\/\\s\/g,'').replace(',','.');\n  if(!\/^(?:\\d+(?:\\.\\d*)?|\\.\\d+)$\/.test(s)) return NaN;\n  const n=Number(s); return finite(n)?n:NaN;\n}\nconst be=(fixed,margin)=>margin>0?Math.ceil(fixed\/margin):(fixed===0&&margin===0?0:null);\nfunction profit(v){\n const support=v.support*v.hour\/60, unit=v.variable+support+v.price*v.fee\/100+v.flat;\n const margin=v.price-unit, fixed=v.fixed;\n return {summary:{revenue:v.customers*v.price,cost:v.customers*unit+fixed,profit:v.customers*margin-fixed,margin,breakEven:be(fixed,margin),minimumPrice:v.customers>0&&v.fee<100?(fixed\/v.customers+v.variable+support+v.flat)\/(1-v.fee\/100):null},\n columns:['scenario','customers','revenue','cost','profit'],rows:[0.5,1,2].map(x=>{const n=Math.round(v.customers*x);return [x+'\u00d7',n,n*v.price,n*unit+fixed,n*margin-fixed];})};\n}\nfunction pricing(v){\n if(Math.abs(v.lightShare+v.mediumShare+v.heavyShare-100)>1e-7)throw Error('shares');\n const profiles=[['light',v.lightShare,v.lightUse],['medium',v.mediumShare,v.mediumUse],['heavy',v.heavyShare,v.heavyUse]];\n const uses=profiles.reduce((s,p)=>s+v.customers*p[1]\/100*p[2],0);\n const packs=profiles.reduce((s,p)=>s+v.customers*p[1]\/100*Math.ceil(p[2]\/v.packSize),0);\n const outcomes=uses*v.success\/100;\n const outcomeBuyers=profiles.reduce((s,p)=>s+(p[2]>0&&v.success>0?v.customers*p[1]\/100:0),0);\n const cost=uses*v.unitCost+v.fixed;\n const data=[['subscription',v.customers*v.subscription,v.customers],['credits',packs*v.packPrice,packs],['outcome',outcomes*v.outcomePrice,outcomeBuyers]];\n const rows=data.map(([name,revenue,invoices])=>{const fees=revenue*v.fee\/100+invoices*v.flat;return [name,revenue,fees,cost,revenue-fees-cost];});\n return {summary:{attempts:uses,outcomes,packs},columns:['model','revenue','fees','operatingCost','profit'],rows};\n}\nfunction automation(v){\n const attempts=v.runs*(1+v.retries\/100), api=attempts*v.api\/1000;\n const rows=['n8n','make','code'].map(k=>{\n const units=attempts*v[k+'Units'];\n const overage=Math.max(0,units-v[k+'Included'])*v[k+'Overage']\/1000;\n const labor=v[k+'Maintenance']*v.hour;\n const setup=v[k+'Setup']*v.hour\/v.months;\n const total=v[k+'Base']+v[k+'Hosting']+overage+labor+setup+api;\n return [k,units,v[k+'Base']+v[k+'Hosting'],overage,labor,setup,api,total,v.runs>0?total\/v.runs:null];\n });\n return {summary:{attempts},columns:['platform','billableUnits','fixedPlanHosting','overage','maintenance','setupMonthly','apiCost','total','perRun'],rows};\n}\nfunction mrr(v){\n const rows=[];let customers=v.customers, current=v.customers*v.price;\n for(let month=1;month<=v.months;month++){\n const opening=current, lost=opening*v.churn\/100, survivors=opening-lost;\n const expansion=survivors*v.expansion\/100, contraction=survivors*v.contraction\/100;\n const fresh=v.newCustomers*v.price;\n current=survivors+expansion-contraction+fresh;\n customers=customers*(1-v.churn\/100)+v.newCustomers;\n const nrr=opening>0?(survivors+expansion-contraction)\/opening*100:null;\n rows.push([month,opening,lost,fresh,expansion,contraction,current,customers,nrr]);\n }\n return {summary:{initialMRR:v.customers*v.price,finalMRR:current,annualRunRate:current*12,finalCustomers:customers},columns:['month','openingMRR','lostMRR','newMRR','expansionMRR','contractionMRR','closingMRR','customersExpected','nrr'],rows};\n}\nfunction digital(v){\n const support=v.support*v.hour\/60, fixed=v.fixed+v.creation*v.hour\/v.months;\n const kept=v.price*(1-v.refunds\/100);\n const fees=v.price*v.fee\/100+v.flat;\n const unit=fees+v.variable+v.acquisition+support;\n const margin=kept-unit;\n const factor=1-v.refunds\/100-v.fee\/100;\n return {summary:{grossRevenue:v.sales*v.price,refundValue:v.sales*v.price*v.refunds\/100,retainedRevenue:v.sales*kept,cost:v.sales*unit+fixed,profit:v.sales*margin-fixed,margin,breakEven:be(fixed,margin),minimumPrice:factor>0&&v.sales>0?(unit-fees+v.flat+fixed\/v.sales)\/factor:null},\n columns:['scenario','sales','retainedRevenue','cost','profit'],rows:[0.5,1,2].map(x=>{const n=Math.round(v.sales*x);return [x+'\u00d7',n,n*kept,n*unit+fixed,n*margin-fixed];})};\n}\nconst api={number,profit,pricing,automation,mrr,digital};\nif(typeof module!=='undefined'&&module.exports)module.exports=api;\nelse scope.LWSaasMath=api;\n})(typeof window!=='undefined'?window:globalThis);\n<\/script><\/p>\n<div class=\"lw-saas\" id=\"saas-mrr\">\n<p>Project monthly recurring revenue, new customers, churn and price changes over up to 60 months.<\/p>\n<p class=\"saas-note\">All monetary amounts use the same currency and exclude VAT. Example values are assumptions, not provider prices. Inputs and calculations stay in the browser.<\/p>\n<p><a href=\"\/blog\/en\/toolbox\/#saas\">All SaaS tools<\/a><\/p>\n<form novalidate>\n<fieldset>\n<legend>Shared assumptions<\/legend>\n<div class=\"saas-fields\">\n<label for=\"saas-mrr-customers\">Starting customers<input type=\"text\" inputmode=\"decimal\" autocomplete=\"off\" data-rc-private data-mp-mask id=\"saas-mrr-customers\" name=\"customers\" value=\"100\"><\/label><br \/>\n<label for=\"saas-mrr-price\">MRR per starting and new customer<input type=\"text\" inputmode=\"decimal\" autocomplete=\"off\" data-rc-private data-mp-mask id=\"saas-mrr-price\" name=\"price\" value=\"19\"><\/label><br \/>\n<label for=\"saas-mrr-newCustomers\">New customers per month<input type=\"text\" inputmode=\"decimal\" autocomplete=\"off\" data-rc-private data-mp-mask id=\"saas-mrr-newCustomers\" name=\"newCustomers\" value=\"10\"><\/label><br \/>\n<label for=\"saas-mrr-churn\">Customer churn per month (%)<input type=\"text\" inputmode=\"decimal\" autocomplete=\"off\" data-rc-private data-mp-mask id=\"saas-mrr-churn\" name=\"churn\" value=\"5\"><\/label><br \/>\n<label for=\"saas-mrr-expansion\">MRR expansion among retained customers (%)<input type=\"text\" inputmode=\"decimal\" autocomplete=\"off\" data-rc-private data-mp-mask id=\"saas-mrr-expansion\" name=\"expansion\" value=\"2\"><\/label><br \/>\n<label for=\"saas-mrr-contraction\">MRR contraction among retained customers (%)<input type=\"text\" inputmode=\"decimal\" autocomplete=\"off\" data-rc-private data-mp-mask id=\"saas-mrr-contraction\" name=\"contraction\" value=\"1\"><\/label><br \/>\n<label for=\"saas-mrr-months\">Horizon in months<input type=\"text\" inputmode=\"decimal\" autocomplete=\"off\" data-rc-private data-mp-mask id=\"saas-mrr-months\" name=\"months\" value=\"24\"><\/label>\n<\/div>\n<\/fieldset>\n<div class=\"saas-actions\"><button type=\"submit\">Calculate<\/button><button type=\"button\" data-reset>Example values<\/button><\/div>\n<\/form>\n<p role=\"alert\" data-status id=\"saas-mrr-status\">\n<div data-result data-rc-private data-mp-mask aria-live=\"polite\" aria-atomic=\"false\"><\/div>\n<p><button type=\"button\" data-csv disabled>Download CSV<\/button><\/p>\n<h2>Calculation model and assumptions<\/h2>\n<p>Churn reduces customers and MRR at the start of the month at the same rate. Expansion and contraction apply separately to retained MRR. New customers enter at month end at the entered net price. NRR = retained MRR after price changes \u00f7 opening MRR \u00d7 100, excluding new customers. ARR = final MRR \u00d7 12, not annual revenue. Customer counts are expectations without intermediate rounding.<\/p>\n<h2>More SaaS tools<\/h2>\n<ul class=\"saas-related\">\n<li><a href=\"\/blog\/en\/toolbox\/saas-profitability-calculator\/\">SaaS Profitability Calculator<\/a><\/li>\n<li><a href=\"\/blog\/en\/toolbox\/saas-pricing-model-simulator\/\">SaaS Pricing Model Simulator<\/a><\/li>\n<li><a href=\"\/blog\/en\/toolbox\/automation-cost-comparator\/\">Automation Cost Comparator<\/a><\/li>\n<li><a href=\"\/blog\/en\/toolbox\/digital-product-profit-calculator\/\">Digital Product Profit Calculator<\/a><\/li>\n<\/ul>\n<h2>Sources and definitions<\/h2>\n<ul>\n<li><a href=\"https:\/\/support.stripe.com\/questions\/understanding-monthly-recurring-revenue-(mrr)-and-annual-recurring-revenue-(arr)\" rel=\"noopener\">Stripe: MRR and ARR<\/a><\/li>\n<\/ul>\n<div class=\"lw-faq\">\n<h2>FAQ<\/h2>\n<h3>Are inputs transmitted?<\/h3>\n<p>The calculator processes inputs locally in the browser. CSV files are also generated locally. The page does not store scenario inputs on the server.<\/p>\n<h3>What are the model limitations?<\/h3>\n<p>MRR normalizes recurring contract values to one month. Payment timing, refunds and one-time sales are not modeled. Fractional customers arise from the statistical churn assumption.<\/p>\n<\/div>\n<p><script type=\"application\/json\" data-config>{\"kind\": \"mrr\", \"slug\": \"mrr-churn-simulator\", \"title\": \"MRR and Churn Simulator\", \"locale\": \"en-GB\", \"fields\": [{\"key\": \"customers\", \"value\": 100, \"label\": \"Starting customers\", \"min\": 0, \"max\": 1000000000.0, \"integer\": true, \"group\": \"common\"}, {\"key\": \"price\", \"value\": 19, \"label\": \"MRR per starting and new customer\", \"min\": 0, \"max\": 1000000000.0, \"integer\": false, \"group\": \"common\"}, {\"key\": \"newCustomers\", \"value\": 10, \"label\": \"New customers per month\", \"min\": 0, \"max\": 1000000000.0, \"integer\": true, \"group\": \"common\"}, {\"key\": \"churn\", \"value\": 5, \"label\": \"Customer churn per month (%)\", \"min\": 0, \"max\": 100, \"integer\": false, \"group\": \"common\"}, {\"key\": \"expansion\", \"value\": 2, \"label\": \"MRR expansion among retained customers (%)\", \"min\": 0, \"max\": 100, \"integer\": false, \"group\": \"common\"}, {\"key\": \"contraction\", \"value\": 1, \"label\": \"MRR contraction among retained customers (%)\", \"min\": 0, \"max\": 100, \"integer\": false, \"group\": \"common\"}, {\"key\": \"months\", \"value\": 24, \"label\": \"Horizon in months\", \"min\": 1, \"max\": 60, \"integer\": true, \"group\": \"common\"}], \"labels\": {\"revenue\": \"Monthly revenue\", \"cost\": \"Monthly cost\", \"profit\": \"Monthly profit\", \"margin\": \"Contribution margin per customer \/ sale\", \"breakEven\": \"Break-even: customers \/ sales\", \"minimumPrice\": \"Cost-covering net price\", \"scenario\": \"Volume\", \"customers\": \"Customers\", \"sales\": \"Sales\", \"attempts\": \"Attempts per month\", \"outcomes\": \"Expected successful outcomes\", \"packs\": \"Credit packs per month\", \"model\": \"Pricing model\", \"fees\": \"Payment fees\", \"operatingCost\": \"Attempt and fixed costs\", \"subscription\": \"Subscription\", \"credits\": \"Credit packs\", \"outcome\": \"Outcome pricing\", \"platform\": \"Platform\", \"billableUnits\": \"Billing units\", \"fixedPlanHosting\": \"Plan and hosting\", \"overage\": \"Overage cost\", \"maintenance\": \"Maintenance cost\", \"setupMonthly\": \"Setup per month\", \"apiCost\": \"External API cost\", \"total\": \"Total cost per month\", \"perRun\": \"Cost per original run\", \"n8n\": \"n8n\", \"make\": \"Make\", \"code\": \"Custom code\", \"initialMRR\": \"Initial MRR\", \"finalMRR\": \"Final MRR\", \"annualRunRate\": \"Final ARR (MRR \u00d7 12)\", \"finalCustomers\": \"Expected final customers\", \"month\": \"Month\", \"openingMRR\": \"Opening MRR\", \"lostMRR\": \"Churned MRR\", \"newMRR\": \"New MRR\", \"expansionMRR\": \"Expansion MRR\", \"contractionMRR\": \"Contraction MRR\", \"closingMRR\": \"Closing MRR\", \"customersExpected\": \"Expected customers\", \"nrr\": \"NRR excluding new customers\", \"grossRevenue\": \"Revenue before refunds\", \"refundValue\": \"Refunds\", \"retainedRevenue\": \"Retained revenue\"}, \"ui\": {\"calculate\": \"Calculate\", \"reset\": \"Example values\", \"csv\": \"Download CSV\", \"results\": \"Result\", \"table\": \"Scenarios and calculation\", \"unavailable\": \"Not calculable\", \"invalid\": \"Invalid value. Allowed range for\", \"integer\": \"(whole number)\", \"shares\": \"The three customer shares must total 100%.\", \"overflow\": \"The inputs produce excessively large values.\", \"changed\": \"Inputs changed. A new calculation updates the result.\", \"undefinedHelp\": \"Not calculable: zero volume, no positive contribution margin, or no cost-covering price at the entered fees.\", \"title\": \"Tool\", \"assumptions\": \"Assumptions\", \"chart\": \"MRR trend; exact values in the table\", \"pricingChart\": \"Monthly profit per pricing model; exact values in the table\", \"method\": \"Calculation model and assumptions\", \"sources\": \"Sources and definitions\", \"faq1\": \"Are inputs transmitted?\", \"faq1a\": \"The calculator processes inputs locally in the browser. CSV files are also generated locally. The page does not store scenario inputs on the server.\", \"faq2\": \"What are the model limitations?\", \"related\": \"More SaaS tools\", \"all\": \"All SaaS tools\"}}<\/script><\/div>\n<p><script>(function(){\n'use strict';\nconst root=document.currentScript.previousElementSibling;\nconst c=JSON.parse(root.querySelector('[data-config]').textContent);\nif(root.dataset.ready)return;root.dataset.ready='1';\nconst form=root.querySelector('form'), result=root.querySelector('[data-result]'), status=root.querySelector('[data-status]'), download=root.querySelector('[data-csv]');\nlet exportRows=null;\nconst fmt=new Intl.NumberFormat(c.locale,{maximumFractionDigits:2});\nconst countFmt=new Intl.NumberFormat(c.locale,{maximumFractionDigits:2});\nconst preciseFmt=new Intl.NumberFormat(c.locale,{maximumFractionDigits:6});\nconst safeMinimum=value=>Math.ceil(value*100)\/100;\nconst e=(tag,text)=>{const n=document.createElement(tag);if(text!==undefined)n.textContent=text;return n;};\nconst label=k=>c.labels[k]||k;\nfunction display(value,key){\n if(value===null)return c.ui.unavailable;\n if(typeof value==='string')return label(value);\n if(key==='minimumPrice')value=safeMinimum(value);\n if(key==='perRun'||key==='margin')return preciseFmt.format(value);\n return (['customers','sales','attempts','outcomes','packs','breakEven','finalCustomers','customersExpected','billableUnits','month'].includes(key)?countFmt:fmt).format(value)+(key==='nrr'?' %':'');\n}\nfunction calculate(){\n status.textContent='';result.replaceChildren();download.disabled=true;exportRows=null;\n root.querySelectorAll('[aria-invalid]').forEach(n=>{n.removeAttribute('aria-invalid');n.removeAttribute('aria-describedby');});\n const v={};let bad=null;\n for(const f of c.fields){\n  const input=form.elements.namedItem(f.key), n=LWSaasMath.number(input.value);\n  if(!Number.isFinite(n)||n<f.min||n>f.max||(f.integer&&!Number.isInteger(n))){\n   input.setAttribute('aria-invalid','true');input.setAttribute('aria-describedby',status.id);\n   if(!bad)bad={input,field:f};\n  }v[f.key]=n;\n }\n if(bad){\n  status.textContent=c.ui.invalid+' '+bad.field.label+': '+bad.field.min+'\u2013'+bad.field.max+(bad.field.integer?' '+c.ui.integer:'');\n  bad.input.focus();return;\n }\n let data;\n try{data=LWSaasMath[c.kind](v);}catch(error){status.textContent=c.ui.shares;return;}\n const values=[...Object.values(data.summary),...data.rows.flat()];\n if(values.some(n=>typeof n==='number'&&!Number.isFinite(n))){status.textContent=c.ui.overflow;return;}\n const h=e('h2',c.ui.results);result.append(h);\n const cards=e('dl');cards.className='saas-metrics';\n for(const [key,value]of Object.entries(data.summary)){\n  const box=e('div');box.append(e('dt',label(key)),e('dd',display(value,key)));cards.append(box);\n }result.append(cards);\n if(c.kind==='mrr'||c.kind==='pricing'){\n  const index=c.kind==='mrr'?6:4;\n  const points=data.rows.map(row=>row[index]);\n  const chart=e('figure');chart.className='saas-chart';\n  const svg=document.createElementNS('http:\/\/www.w3.org\/2000\/svg','svg');\n  svg.setAttribute('viewBox','0 0 600 160');svg.setAttribute('role','img');svg.setAttribute('aria-label',c.ui.chart);\n  const max=Math.max(1,...points.map(Math.abs));\n  if(c.kind==='mrr'){\n   const path=document.createElementNS(svg.namespaceURI,'polyline');\n   path.setAttribute('points',points.map((n,i)=>(20+i*560\/Math.max(1,points.length-1))+','+(140-n\/max*120)).join(' '));\n   path.setAttribute('fill','none');path.setAttribute('stroke','currentColor');path.setAttribute('stroke-width','3');svg.append(path);\n  }else{\n   for(let i=0;i<points.length;i++){\n    const rect=document.createElementNS(svg.namespaceURI,'rect');\n    rect.setAttribute('x',30+i*195);rect.setAttribute('width','140');\n    rect.setAttribute('y',points[i]>=0?80-points[i]\/max*60:80);\n    rect.setAttribute('height',Math.abs(points[i])\/max*60);rect.setAttribute('fill','currentColor');\n    const title=document.createElementNS(svg.namespaceURI,'title');title.textContent=label(data.rows[i][0])+': '+display(points[i],'profit');rect.append(title);svg.append(rect);\n   }\n   const axis=document.createElementNS(svg.namespaceURI,'path');axis.setAttribute('d','M20 80H580');axis.setAttribute('stroke','currentColor');svg.append(axis);\n  }\n  chart.append(svg,e('figcaption',c.ui.chart));result.append(chart);\n }\n const scroll=e('div');scroll.className='saas-table-wrap';scroll.tabIndex=0;\n scroll.setAttribute('role','region');scroll.setAttribute('aria-label',c.ui.table);\n const table=e('table');table.append(e('caption',c.ui.table));\n const head=e('thead'), hr=e('tr');\n data.columns.forEach(k=>{const th=e('th',label(k));th.scope='col';hr.append(th);});head.append(hr);table.append(head);\n const body=e('tbody');\n data.rows.forEach(row=>{const tr=e('tr');row.forEach((value,i)=>{const cell=e(i===0?'th':'td',display(value,data.columns[i]));if(i===0)cell.scope='row';tr.append(cell);});body.append(tr);});\n table.append(body);scroll.append(table);result.append(scroll);\n if(Object.values(data.summary).includes(null)||data.rows.flat().includes(null))result.append(e('p',c.ui.undefinedHelp));\n exportRows=[[c.ui.title,c.title],[c.ui.assumptions]];\n for(const f of c.fields)exportRows.push([f.label,v[f.key]]);\n exportRows.push([],['',c.ui.results]);\n for(const [key,value]of Object.entries(data.summary))exportRows.push([label(key),value===null?c.ui.unavailable:key==='minimumPrice'?safeMinimum(value):value]);\n exportRows.push([],data.columns.map(label),...data.rows.map(row=>row.map(x=>typeof x==='string'?label(x):x===null?c.ui.unavailable:x)));\n download.disabled=false;\n root.dispatchEvent(new CustomEvent('saas-calculated',{detail:data}));\n}\nform.addEventListener('submit',event=>{event.preventDefault();calculate();});\nform.addEventListener('input',()=>{exportRows=null;download.disabled=true;status.textContent=c.ui.changed;result.replaceChildren();});\nroot.querySelector('[data-reset]').addEventListener('click',()=>{form.reset();calculate();});\ndownload.addEventListener('click',()=>{\n if(!exportRows)return;\n const csv='\\uFEFF'+exportRows.map(row=>row.map(x=>'\"'+String(x).replace(\/\"\/g,'\"\"')+'\"').join(';')).join('\\r\\n');\n const url=URL.createObjectURL(new Blob([csv],{type:'text\/csv;charset=utf-8'}));\n const a=e('a');a.href=url;a.download=c.slug+'.csv';root.append(a);a.click();a.remove();setTimeout(()=>URL.revokeObjectURL(url),1000);\n});\nroot._saasCalculate=calculate;\ncalculate();\n})();\n<\/script><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Project monthly recurring revenue, new customers, churn and price changes over up to 60 months.<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":38,"menu_order":4,"comment_status":"closed","ping_status":"closed","template":"template-tool-base.php","meta":{"footnotes":""},"tags":[93051],"class_list":["post-21216","page","type-page","status-publish","hentry","tag-saas-tools-en"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v28.1 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>MRR and Churn Simulator | Lukas Wojcik<\/title>\n<meta name=\"description\" content=\"Project monthly recurring revenue, new customers, churn and price changes over up to 60 months.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/mrr-churn-simulator\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"MRR and Churn Simulator | Lukas Wojcik\" \/>\n<meta property=\"og:description\" content=\"Project monthly recurring revenue, new customers, churn and price changes over up to 60 months.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/mrr-churn-simulator\/\" \/>\n<meta property=\"og:site_name\" content=\"Lukas Wojcik - Blog\" \/>\n<meta property=\"article:modified_time\" content=\"2026-10-05T13:14:23+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/08\/og-default.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"1200\" \/>\n\t<meta property=\"og:image:height\" content=\"630\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data1\" content=\"1 minute\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/toolbox\\\/mrr-churn-simulator\\\/\",\"url\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/toolbox\\\/mrr-churn-simulator\\\/\",\"name\":\"MRR and Churn Simulator | Lukas Wojcik\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#website\"},\"datePublished\":\"2026-10-05T12:51:21+00:00\",\"dateModified\":\"2026-10-05T13:14:23+00:00\",\"description\":\"Project monthly recurring revenue, new customers, churn and price changes over up to 60 months.\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/toolbox\\\/mrr-churn-simulator\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/toolbox\\\/mrr-churn-simulator\\\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/toolbox\\\/mrr-churn-simulator\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Toolbox\",\"item\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/toolbox\\\/\"},{\"@type\":\"ListItem\",\"position\":3,\"name\":\"MRR and Churn Simulator\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#website\",\"url\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/\",\"name\":\"Lukas Wojcik - Blog\",\"description\":\"\",\"publisher\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#\\\/schema\\\/person\\\/895f7604f9b6b71aad9bba33af28d0f9\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":[\"Person\",\"Organization\"],\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#\\\/schema\\\/person\\\/895f7604f9b6b71aad9bba33af28d0f9\",\"name\":\"Lukas Wojcik\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/lw-x2.jpg\",\"url\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/lw-x2.jpg\",\"contentUrl\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/lw-x2.jpg\",\"width\":424,\"height\":636,\"caption\":\"Lukas Wojcik\"},\"logo\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/lw-x2.jpg\"},\"sameAs\":[\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\"]}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"MRR and Churn Simulator | Lukas Wojcik","description":"Project monthly recurring revenue, new customers, churn and price changes over up to 60 months.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/mrr-churn-simulator\/","og_locale":"en_US","og_type":"article","og_title":"MRR and Churn Simulator | Lukas Wojcik","og_description":"Project monthly recurring revenue, new customers, churn and price changes over up to 60 months.","og_url":"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/mrr-churn-simulator\/","og_site_name":"Lukas Wojcik - Blog","article_modified_time":"2026-10-05T13:14:23+00:00","og_image":[{"width":1200,"height":630,"url":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/08\/og-default.jpg","type":"image\/jpeg"}],"twitter_card":"summary_large_image","twitter_misc":{"Est. reading time":"1 minute"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/mrr-churn-simulator\/","url":"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/mrr-churn-simulator\/","name":"MRR and Churn Simulator | Lukas Wojcik","isPartOf":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/#website"},"datePublished":"2026-10-05T12:51:21+00:00","dateModified":"2026-10-05T13:14:23+00:00","description":"Project monthly recurring revenue, new customers, churn and price changes over up to 60 months.","breadcrumb":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/mrr-churn-simulator\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/mrr-churn-simulator\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/mrr-churn-simulator\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.lukaswojcik.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Toolbox","item":"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/"},{"@type":"ListItem","position":3,"name":"MRR and Churn Simulator"}]},{"@type":"WebSite","@id":"https:\/\/www.lukaswojcik.com\/blog\/#website","url":"https:\/\/www.lukaswojcik.com\/blog\/","name":"Lukas Wojcik - Blog","description":"","publisher":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/#\/schema\/person\/895f7604f9b6b71aad9bba33af28d0f9"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.lukaswojcik.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":["Person","Organization"],"@id":"https:\/\/www.lukaswojcik.com\/blog\/#\/schema\/person\/895f7604f9b6b71aad9bba33af28d0f9","name":"Lukas Wojcik","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/07\/lw-x2.jpg","url":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/07\/lw-x2.jpg","contentUrl":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/07\/lw-x2.jpg","width":424,"height":636,"caption":"Lukas Wojcik"},"logo":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/07\/lw-x2.jpg"},"sameAs":["https:\/\/www.lukaswojcik.com\/blog"]}]}},"_links":{"self":[{"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/pages\/21216","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/comments?post=21216"}],"version-history":[{"count":3,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/pages\/21216\/revisions"}],"predecessor-version":[{"id":21321,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/pages\/21216\/revisions\/21321"}],"up":[{"embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/pages\/38"}],"wp:attachment":[{"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/media?parent=21216"}],"wp:term":[{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/tags?post=21216"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}