{"id":21219,"date":"2026-10-05T14:51:25","date_gmt":"2026-10-05T12:51:25","guid":{"rendered":"https:\/\/www.lukaswojcik.com\/blog\/?page_id=21219"},"modified":"2026-10-05T15:14:26","modified_gmt":"2026-10-05T13:14:26","slug":"digital-product-profit-calculator","status":"publish","type":"page","link":"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/digital-product-profit-calculator\/","title":{"rendered":"Digital Product Profit Calculator"},"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-digital\">\n<p>Calculate contribution margin, minimum price and break-even for templates, data packs and mini-tools.<\/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-digital-sales\">Sales before refunds per month<input type=\"text\" inputmode=\"decimal\" autocomplete=\"off\" data-rc-private data-mp-mask id=\"saas-digital-sales\" name=\"sales\" value=\"100\"><\/label><br \/>\n<label for=\"saas-digital-price\">Net price per sale<input type=\"text\" inputmode=\"decimal\" autocomplete=\"off\" data-rc-private data-mp-mask id=\"saas-digital-price\" name=\"price\" value=\"29\"><\/label><br \/>\n<label for=\"saas-digital-refunds\">Fully refunded sales (%)<input type=\"text\" inputmode=\"decimal\" autocomplete=\"off\" data-rc-private data-mp-mask id=\"saas-digital-refunds\" name=\"refunds\" value=\"5\"><\/label><br \/>\n<label for=\"saas-digital-variable\">Delivery cost per original sale<input type=\"text\" inputmode=\"decimal\" autocomplete=\"off\" data-rc-private data-mp-mask id=\"saas-digital-variable\" name=\"variable\" value=\"1\"><\/label><br \/>\n<label for=\"saas-digital-acquisition\">Acquisition cost per original sale<input type=\"text\" inputmode=\"decimal\" autocomplete=\"off\" data-rc-private data-mp-mask id=\"saas-digital-acquisition\" name=\"acquisition\" value=\"5\"><\/label><br \/>\n<label for=\"saas-digital-support\">Support minutes per original sale<input type=\"text\" inputmode=\"decimal\" autocomplete=\"off\" data-rc-private data-mp-mask id=\"saas-digital-support\" name=\"support\" value=\"5\"><\/label><br \/>\n<label for=\"saas-digital-hour\">Hourly cost<input type=\"text\" inputmode=\"decimal\" autocomplete=\"off\" data-rc-private data-mp-mask id=\"saas-digital-hour\" name=\"hour\" value=\"40\"><\/label><br \/>\n<label for=\"saas-digital-fee\">Payment fee (%)<input type=\"text\" inputmode=\"decimal\" autocomplete=\"off\" data-rc-private data-mp-mask id=\"saas-digital-fee\" name=\"fee\" value=\"3\"><\/label><br \/>\n<label for=\"saas-digital-flat\">Fixed fee per payment<input type=\"text\" inputmode=\"decimal\" autocomplete=\"off\" data-rc-private data-mp-mask id=\"saas-digital-flat\" name=\"flat\" value=\"0.3\"><\/label><br \/>\n<label for=\"saas-digital-fixed\">Fixed costs per month<input type=\"text\" inputmode=\"decimal\" autocomplete=\"off\" data-rc-private data-mp-mask id=\"saas-digital-fixed\" name=\"fixed\" value=\"100\"><\/label><br \/>\n<label for=\"saas-digital-creation\">One-time creation hours<input type=\"text\" inputmode=\"decimal\" autocomplete=\"off\" data-rc-private data-mp-mask id=\"saas-digital-creation\" name=\"creation\" value=\"40\"><\/label><br \/>\n<label for=\"saas-digital-months\">Horizon \/ cost allocation in months<input type=\"text\" inputmode=\"decimal\" autocomplete=\"off\" data-rc-private data-mp-mask id=\"saas-digital-months\" name=\"months\" value=\"12\"><\/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-digital-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>Retained revenue = price \u00d7 sales \u00d7 (1 \u2212 refund rate). Payment fees, delivery, acquisition and support apply to all original sales. Payment fees are assumed non-refundable. Creation cost = hours \u00d7 hourly cost \u00f7 months. Monthly profit = sales \u00d7 contribution margin \u2212 ongoing fixed costs \u2212 amortized creation cost. Minimum price accounts for refunds and percentage fees. The displayed minimum price is rounded up to the next hundredth.<\/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\/mrr-churn-simulator\/\">MRR and Churn Simulator<\/a><\/li>\n<\/ul>\n<h2>Sources and definitions<\/h2>\n<ul>\n<li><a href=\"https:\/\/docs.stripe.com\/refunds\" rel=\"noopener\">Stripe: Refunds<\/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>Allocating creation costs to one month charges the first monthly result in full. Different refund rules require adjusted fee assumptions. A refund rate plus fee rate of 100% or more prevents a positive cost-covering minimum price.<\/p>\n<\/div>\n<p><script type=\"application\/json\" data-config>{\"kind\": \"digital\", \"slug\": \"digital-product-profit-calculator\", \"title\": \"Digital Product Profit Calculator\", \"locale\": \"en-GB\", \"fields\": [{\"key\": \"sales\", \"value\": 100, \"label\": \"Sales before refunds per month\", \"min\": 0, \"max\": 1000000000.0, \"integer\": true, \"group\": \"common\"}, {\"key\": \"price\", \"value\": 29, \"label\": \"Net price per sale\", \"min\": 0, \"max\": 1000000000.0, \"integer\": false, \"group\": \"common\"}, {\"key\": \"refunds\", \"value\": 5, \"label\": \"Fully refunded sales (%)\", \"min\": 0, \"max\": 100, \"integer\": false, \"group\": \"common\"}, {\"key\": \"variable\", \"value\": 1, \"label\": \"Delivery cost per original sale\", \"min\": 0, \"max\": 1000000000.0, \"integer\": false, \"group\": \"common\"}, {\"key\": \"acquisition\", \"value\": 5, \"label\": \"Acquisition cost per original sale\", \"min\": 0, \"max\": 1000000000.0, \"integer\": false, \"group\": \"common\"}, {\"key\": \"support\", \"value\": 5, \"label\": \"Support minutes per original sale\", \"min\": 0, \"max\": 1000000000.0, \"integer\": false, \"group\": \"common\"}, {\"key\": \"hour\", \"value\": 40, \"label\": \"Hourly cost\", \"min\": 0, \"max\": 1000000000.0, \"integer\": false, \"group\": \"common\"}, {\"key\": \"fee\", \"value\": 3, \"label\": \"Payment fee (%)\", \"min\": 0, \"max\": 100, \"integer\": false, \"group\": \"common\"}, {\"key\": \"flat\", \"value\": 0.3, \"label\": \"Fixed fee per payment\", \"min\": 0, \"max\": 1000000000.0, \"integer\": false, \"group\": \"common\"}, {\"key\": \"fixed\", \"value\": 100, \"label\": \"Fixed costs per month\", \"min\": 0, \"max\": 1000000000.0, \"integer\": false, \"group\": \"common\"}, {\"key\": \"creation\", \"value\": 40, \"label\": \"One-time creation hours\", \"min\": 0, \"max\": 1000000000.0, \"integer\": false, \"group\": \"common\"}, {\"key\": \"months\", \"value\": 12, \"label\": \"Horizon \/ cost allocation in months\", \"min\": 1, \"max\": 120, \"integer\": true, \"group\": \"common\"}], \"labels\": {\"revenue\": \"Monthly revenue\", \"cost\": \"Monthly cost\", \"profit\": \"Monthly profit\", \"margin\": \"Contribution margin per sale\", \"breakEven\": \"Break-even 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>Calculate contribution margin, minimum price and break-even for templates, data packs and mini-tools.<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":38,"menu_order":5,"comment_status":"closed","ping_status":"closed","template":"template-tool-base.php","meta":{"footnotes":""},"tags":[93051],"class_list":["post-21219","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>Digital Product Profit Calculator | Lukas Wojcik<\/title>\n<meta name=\"description\" content=\"Calculate contribution margin, minimum price and break-even for templates, data packs and mini-tools.\" \/>\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\/digital-product-profit-calculator\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Digital Product Profit Calculator | Lukas Wojcik\" \/>\n<meta property=\"og:description\" content=\"Calculate contribution margin, minimum price and break-even for templates, data packs and mini-tools.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/digital-product-profit-calculator\/\" \/>\n<meta property=\"og:site_name\" content=\"Lukas Wojcik - Blog\" \/>\n<meta property=\"article:modified_time\" content=\"2026-10-05T13:14:26+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\\\/digital-product-profit-calculator\\\/\",\"url\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/toolbox\\\/digital-product-profit-calculator\\\/\",\"name\":\"Digital Product Profit Calculator | Lukas Wojcik\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#website\"},\"datePublished\":\"2026-10-05T12:51:25+00:00\",\"dateModified\":\"2026-10-05T13:14:26+00:00\",\"description\":\"Calculate contribution margin, minimum price and break-even for templates, data packs and mini-tools.\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/toolbox\\\/digital-product-profit-calculator\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/toolbox\\\/digital-product-profit-calculator\\\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/toolbox\\\/digital-product-profit-calculator\\\/#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\":\"Digital Product Profit Calculator\"}]},{\"@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":"Digital Product Profit Calculator | Lukas Wojcik","description":"Calculate contribution margin, minimum price and break-even for templates, data packs and mini-tools.","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\/digital-product-profit-calculator\/","og_locale":"en_US","og_type":"article","og_title":"Digital Product Profit Calculator | Lukas Wojcik","og_description":"Calculate contribution margin, minimum price and break-even for templates, data packs and mini-tools.","og_url":"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/digital-product-profit-calculator\/","og_site_name":"Lukas Wojcik - Blog","article_modified_time":"2026-10-05T13:14:26+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\/digital-product-profit-calculator\/","url":"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/digital-product-profit-calculator\/","name":"Digital Product Profit Calculator | Lukas Wojcik","isPartOf":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/#website"},"datePublished":"2026-10-05T12:51:25+00:00","dateModified":"2026-10-05T13:14:26+00:00","description":"Calculate contribution margin, minimum price and break-even for templates, data packs and mini-tools.","breadcrumb":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/digital-product-profit-calculator\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/digital-product-profit-calculator\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/digital-product-profit-calculator\/#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":"Digital Product Profit Calculator"}]},{"@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\/21219","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=21219"}],"version-history":[{"count":3,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/pages\/21219\/revisions"}],"predecessor-version":[{"id":21324,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/pages\/21219\/revisions\/21324"}],"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=21219"}],"wp:term":[{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/tags?post=21219"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}