Mortgage Payoff Calculator

Free Monthly Home Loan Calculator.

Mortgage Payoff Calculator

Mortgage Payoff Calculator

$
%
years
months
$
$
%
Extra Payments
$
$
$

Mortgage Payoff Summary

Based on your inputted interest rate of 6.00%

Payoff details for your mortgage, including the impact of extra payments or bi-weekly payments on interest savings and payoff time.

Remaining Payments: 0
Payoff Amount
$0.00
Total Interest Paid
$0.00
Interest Savings
$0.00
Payoff Time
0 yrs, 0 mos
OriginalWith Payoff
Monthly Pay$0.00$0.00
Total Payments$0.00$0.00
Total Interest$0.00$0.00
Remaining Payments$0.00$0.00
Remaining Interest$0.00$0.00
Payoff Time0 yrs, 0 mos0 yrs, 0 mos

Loan Balance Over Time

Loan Balance: $0.00
Principal Paid: $0.00
Interest Paid: $0.00

Payoff Schedule Breakdown

Payment details up to the payoff date.

First payment: July 2025 | Payoff date: June 2035
| |
`); printWindow.document.close(); printWindow.print(); });exportCsvBtn.addEventListener('click', () => { const rows = [["Year", "Month", "Principal", "Interest", "Remaining Balance"]]; payoffTableBody.querySelectorAll('details').forEach(detail => { const year = detail.querySelector('summary .summary-year').textContent; detail.querySelectorAll('.monthly-table tbody tr').forEach(row => { const cells = Array.from(row.querySelectorAll('td')).map(td => { const text = td.textContent.replace(/[^0-9A-Za-z\s.]/g, ''); return `"${text.replace(/"/g, '""')}"`; }); rows.push([year, ...cells]); }); }); const csvContent = "data:text/csv;charset=utf-8," + rows.map(e => e.join(",")).join("\n"); const link = document.createElement("a"); link.setAttribute("href", encodeURI(csvContent)); link.setAttribute("download", "payoff_schedule.csv"); document.body.appendChild(link); link.click(); document.body.removeChild(link); });// --- CORE LOGIC --- function getFormValues() { const isKnownTerm = tabKnownTerm.classList.contains('active'); if (isKnownTerm) { return { mode: 'known', loanAmount: parseCurrency(knownLoanAmountInput.value), loanTerm: parseInt(knownLoanTermSelect.value, 10), interestRate: parseFloat(knownInterestRateInput.value) || 0, remainingYears: parseFloat(knownRemainingYearsInput.value) || 0, remainingMonths: parseFloat(knownRemainingMonthsInput.value) || 0, extraMonthly: parseCurrency(extraMonthlyInput.value), extraYearly: parseCurrency(extraYearlyInput.value), oneTimeAmount: parseCurrency(oneTimeAmountInput.value), oneTimeDate: oneTimeDateInput.value ? new Date(oneTimeDateInput.value) : new Date(), biweeklyPayments: biweeklyPaymentsCheckbox.checked }; } else { return { mode: 'unknown', principalBalance: parseCurrency(unknownPrincipalBalanceInput.value), monthlyPayment: parseCurrency(unknownMonthlyPaymentInput.value), interestRate: parseFloat(unknownInterestRateInput.value) || 0, extraMonthly: parseCurrency(extraMonthlyInput.value), extraYearly: parseCurrency(extraYearlyInput.value), oneTimeAmount: parseCurrency(oneTimeAmountInput.value), oneTimeDate: oneTimeDateInput.value ? new Date(oneTimeDateInput.value) : new Date(), biweeklyPayments: biweeklyPaymentsCheckbox.checked }; } }function calculateAndDisplay() { loadingSpinner.classList.add('active'); setTimeout(() => { const form = getFormValues(); let monthlyPayment, principal, remainingPayments, maxPayments;if (form.mode === 'known') { principal = form.loanAmount; maxPayments = form.loanTerm * 12; remainingPayments = Math.min(maxPayments, Math.floor(form.remainingYears * 12 + form.remainingMonths)); const monthlyRate = form.interestRate / 1200; if (principal > 0 && maxPayments > 0 && monthlyRate > 0) { monthlyPayment = principal * (monthlyRate * Math.pow(1 + monthlyRate, maxPayments)) / (Math.pow(1 + monthlyRate, maxPayments) - 1); } else if (principal > 0 && maxPayments > 0) { monthlyPayment = principal / maxPayments; } else { monthlyPayment = 0; } monthlyPayment = Number(monthlyPayment.toFixed(2)); } else { principal = form.principalBalance; monthlyPayment = form.monthlyPayment; remainingPayments = estimateRemainingPayments(principal, monthlyPayment, form.interestRate / 1200); maxPayments = remainingPayments; }const startDate = new Date(); const endDate = new Date(startDate); endDate.setMonth(endDate.getMonth() + remainingPayments);const payoffData = generatePayoffData( principal, form.interestRate / 1200, monthlyPayment, maxPayments, form.biweeklyPayments ? monthlyPayment / 2 : form.extraMonthly, form.extraYearly, form.oneTimeAmount, form.oneTimeDate, startDate, endDate, form.biweeklyPayments );const noExtraPayoffData = generatePayoffData( principal, form.interestRate / 1200, monthlyPayment, maxPayments, 0, 0, 0, startDate, startDate, endDate, false );updateUI(form, monthlyPayment, payoffData, noExtraPayoffData); loadingSpinner.classList.remove('active'); }, 50); }function estimateRemainingPayments(principal, monthlyPayment, monthlyRate) { if (principal <= 0 || monthlyPayment <= 0 || monthlyRate < 0) return 0; if (monthlyRate === 0) return Math.ceil(principal / monthlyPayment); const n = Math.log(monthlyPayment / (monthlyPayment - principal * monthlyRate)) / Math.log(1 + monthlyRate); return Math.ceil(n); }function generatePayoffData(principal, monthlyRate, monthlyPayment, maxPayments, extraMonthly, extraYearly, oneTimeAmount, oneTimeDate, startDate, endDate, isBiweekly) { if (principal <= 0 || maxPayments <= 0) { return { yearlyData: {}, monthlyData: [], totalInterest: 0, totalPayments: 0, numPayments: 0, firstPaymentDate: new Date(startDate), lastPaymentDate: new Date(startDate), payoffAmount: 0 }; }let balance = principal; let totalInterest = 0; let totalPayments = 0; const firstPaymentDate = new Date(startDate.getFullYear(), startDate.getMonth() + 1, 1); let currentDate = new Date(firstPaymentDate); let lastPaymentDate = currentDate; const monthlyData = []; const yearlyData = {}; let oneTimeApplied = false; let paymentCount = 0; let biweeklyCounter = 0;while (balance > 0.01 && paymentCount < maxPayments && currentDate <= endDate) { const year = currentDate.getFullYear(); const isYearStart = currentDate.getMonth() === 0; const isBiweeklyPayment = isBiweekly && (biweeklyCounter % 2 === 0);const interestPayment = balance * monthlyRate; let principalPayment = isBiweeklyPayment ? (monthlyPayment / 2) : (monthlyPayment + extraMonthly);if (isYearStart && extraYearly > 0 && currentDate > firstPaymentDate) { principalPayment += extraYearly; }if (!oneTimeApplied && oneTimeAmount > 0 && currentDate >= oneTimeDate) { principalPayment += oneTimeAmount; oneTimeApplied = true; }if (principalPayment > balance) { principalPayment = balance; }balance -= principalPayment; totalInterest += interestPayment; totalPayments += principalPayment + interestPayment; lastPaymentDate = new Date(currentDate); paymentCount++;monthlyData.push({ date: new Date(currentDate), interest: Number(interestPayment.toFixed(2)), principal: Number(principalPayment.toFixed(2)), balance: Number(Math.max(balance, 0).toFixed(2)) });if (!yearlyData[year]) { yearlyData[year] = { months: [], totalPrincipal: 0, totalInterest: 0, endBalance: 0 }; } yearlyData[year].months.push(monthlyData[monthlyData.length - 1]); yearlyData[year].totalPrincipal += principalPayment; yearlyData[year].totalInterest += interestPayment; yearlyData[year].endBalance = Math.max(balance, 0);if (isBiweekly) { biweeklyCounter++; currentDate.setDate(currentDate.getDate() + 14); if (biweeklyCounter % 26 === 0) { paymentCount++; monthlyData.push({ date: new Date(currentDate), interest: 0, principal: monthlyPayment, balance: Number(Math.max(balance - monthlyPayment, 0).toFixed(2)) }); yearlyData[year].months.push(monthlyData[monthlyData.length - 1]); yearlyData[year].totalPrincipal += monthlyPayment; yearlyData[year].endBalance = Math.max(balance, 0); balance -= monthlyPayment; totalPayments += monthlyPayment; } } else { currentDate.setMonth(currentDate.getMonth() + 1); } }Object.keys(yearlyData).forEach(year => { yearlyData[year].totalPrincipal = Number(yearlyData[year].totalPrincipal.toFixed(2)); yearlyData[year].totalInterest = Number(yearlyData[year].totalInterest.toFixed(2)); yearlyData[year].endBalance = Number(yearlyData[year].endBalance.toFixed(2)); });const payoffAmount = monthlyData.length > 0 ? monthlyData[monthlyData.length - 1].balance : principal;return { yearlyData, monthlyData, totalInterest: Number(totalInterest.toFixed(2)), totalPayments: Number(totalPayments.toFixed(2)), numPayments: paymentCount, firstPaymentDate, lastPaymentDate, payoffAmount }; }function updateUI(form, monthlyPayment, payoffData, noExtraPayoffData) { const { yearlyData, monthlyData, totalInterest, totalPayments, numPayments, firstPaymentDate, lastPaymentDate, payoffAmount } = payoffData; const noExtraTotalInterest = noExtraPayoffData.totalInterest; const noExtraTotalPayments = noExtraPayoffData.totalPayments; const noExtraNumPayments = noExtraPayoffData.numPayments; const noExtraPayoffAmount = noExtraPayoffData.payoffAmount;dynamicRateEl.textContent = form.interestRate.toFixed(2); summaryNumPaymentsEl.textContent = numPayments; summaryPayoffAmountEl.textContent = formatCurrency(payoffAmount); summaryTotalInterestEl.textContent = formatCurrency(totalInterest); summaryInterestSavingsEl.textContent = formatCurrency(Math.max(noExtraTotalInterest - totalInterest, 0)); summaryPayoffTimeEl.textContent = formatTerm(numPayments);const totalPrincipal = Number((form.mode === 'known' ? form.loanAmount : form.principalBalance - (monthlyData[monthlyData.length - 1]?.balance || 0)).toFixed(2)); legendBalanceEl.textContent = formatCurrency(monthlyData[monthlyData.length - 1]?.balance || 0); legendPrincipalEl.textContent = formatCurrency(totalPrincipal); legendInterestEl.textContent = formatCurrency(totalInterest);compMonthlyPayOrigEl.textContent = formatCurrency(monthlyPayment); compMonthlyPayNewEl.textContent = formatCurrency(monthlyPayment + (form.biweeklyPayments ? monthlyPayment / 12 : form.extraMonthly)); compTotalPaymentsOrigEl.textContent = formatCurrency(noExtraTotalPayments); compTotalPaymentsNewEl.textContent = formatCurrency(totalPayments); compTotalInterestOrigEl.textContent = formatCurrency(noExtraTotalInterest); compTotalInterestNewEl.textContent = formatCurrency(totalInterest); compRemainingPaymentsOrigEl.textContent = formatCurrency(noExtraPayoffAmount + noExtraTotalInterest); compRemainingPaymentsNewEl.textContent = formatCurrency(payoffAmount + totalInterest); compRemainingInterestOrigEl.textContent = formatCurrency(noExtraTotalInterest); compRemainingInterestNewEl.textContent = formatCurrency(totalInterest); compPayoffTimeOrigEl.textContent = formatTerm(noExtraNumPayments); compPayoffTimeNewEl.textContent = formatTerm(numPayments);firstPaymentDateEl.textContent = firstPaymentDate.toLocaleString('default', { month: 'long', year: 'numeric' }); lastPaymentDateEl.textContent = lastPaymentDate.toLocaleString('default', { month: 'long', year: 'numeric' });updatePayoffGraph(yearlyData, form.mode === 'known' ? form.loanAmount : form.principalBalance, totalInterest, noExtraPayoffData); updatePayoffTable(yearlyData); }function updatePayoffGraph(yearlyData, loanAmount, totalInterest, noExtraPayoffData) { const svg = document.getElementById('payoff-chart-svg'); svg.innerHTML = ''; if (Object.keys(yearlyData).length === 0 || loanAmount <= 0) { svg.innerHTML = 'No data to display'; return; }const allMonths = Object.values(yearlyData).flatMap(y => y.months); const noExtraMonths = Object.values(noExtraPayoffData.yearlyData).flatMap(y => y.months); if (allMonths.length === 0) return;const width = 360; const height = 220, m = { top: 20, right: 20, bottom: 35, left: 50 }; const w = width - m.left - m.right, h = height - m.top - m.bottom; const maxY = Math.max(loanAmount, totalInterest, noExtraPayoffData.totalInterest, 1);const g = document.createElementNS("http://www.w3.org/2000/svg", "g"); g.setAttribute("transform", `translate(${m.left},${m.top})`); svg.appendChild(g);const maxMonths = Math.max(allMonths.length, noExtraMonths.length); const xScale = i => (i / (maxMonths - 1)) * w; const yScale = v => h - (v / maxY) * h;const createPath = (data, color, isDashed = false) => { const path = document.createElementNS("http://www.w3.org/2000/svg", "path"); let d = `M0,${yScale(data[0])}`; for (let i = 1; i < data.length; i++) { if (isFinite(data[i]) && data[i] >= 0) { d += ` L${xScale(i)},${yScale(data[i])}`; } } path.setAttribute('d', d); path.setAttribute('class', 'line'); path.style.stroke = color; path.style.strokeWidth = '2.5'; path.style.opacity = isDashed ? '0.5' : '0.9'; if (isDashed) path.style.strokeDasharray = '5,5'; return path; };let cumulativePrincipal = 0, cumulativeInterest = 0; const balanceData = allMonths.map(d => d.balance < 0 ? 0 : d.balance); const principalData = allMonths.map(d => { cumulativePrincipal += d.principal; return Math.min(cumulativePrincipal, maxY); }); const interestData = allMonths.map(d => { cumulativeInterest += d.interest; return Math.min(cumulativeInterest, maxY); }); const oldBalanceData = noExtraMonths.map(d => d.balance < 0 ? 0 : d.balance);[createPath(oldBalanceData, 'var(--br-chart-balance)', true), createPath(balanceData, 'var(--br-chart-balance)'), createPath(principalData, 'var(--br-chart-principal)'), createPath(interestData, 'var(--br-chart-interest)')].forEach(path => g.appendChild(path));for (let i = 0; i <= 5; i++) { const y = h * (i / 5); const line = document.createElementNS("http://www.w3.org/2000/svg", "line"); line.setAttribute('x1', 0); line.setAttribute('x2', w); line.setAttribute('y1', y); line.setAttribute('y2', y); line.setAttribute('class', 'grid'); g.appendChild(line); const text = document.createElementNS("http://www.w3.org/2000/svg", "text"); text.setAttribute('x', -10); text.setAttribute('y', y + 4); text.setAttribute('class', 'axis-label'); text.setAttribute('text-anchor', 'end'); text.textContent = `$${(maxY * (1 - i / 5) / 1000).toFixed(0)}k`; g.appendChild(text); }const yearKeys = Object.keys(yearlyData).sort(); const step = Math.max(1, Math.ceil(yearKeys.length / 6)); for (let i = 0; i < yearKeys.length; i += step) { const year = yearKeys[i]; const monthIndex = allMonths.findIndex(d => d.date.getFullYear() == year); if (monthIndex < 0) continue; const x = xScale(monthIndex); const line = document.createElementNS("http://www.w3.org/2000/svg", "line"); line.setAttribute('x1', x); line.setAttribute('x2', x); line.setAttribute('y1', 0); line.setAttribute('y2', h); line.setAttribute('class', 'grid'); g.appendChild(line); const text = document.createElementNS("http://www.w3.org/2000/svg", "text"); text.setAttribute('x', x); text.setAttribute('y', h + 20); text.setAttribute('class', 'axis-label'); text.setAttribute('text-anchor', 'middle'); text.textContent = year; g.appendChild(text); } }function updatePayoffTable(yearlyData) { payoffTableBody.innerHTML = ''; Object.keys(yearlyData).sort().forEach(year => { const yearData = yearlyData[year]; const details = document.createElement('details'); details.innerHTML = ` ${year} ${formatCurrency(yearData.totalPrincipal)} ${formatCurrency(yearData.totalInterest)} ${formatCurrency(yearData.endBalance)}
${yearData.months.map(row => ` `).join('')}
MonthPrincipalInterestRemaining Balance
${row.date.toLocaleString('default', { month: 'long' })}${formatCurrency(row.principal)}${formatCurrency(row.interest)}${formatCurrency(row.balance)}
`; payoffTableBody.appendChild(details); }); }calculateAndDisplay(); });

Why Choose Our Free PDF Merger?

Completely Free

Merge unlimited PDFs with no fees, no watermarks, and no hidden costs.

Lightning Fast

Combine files in seconds with our intuitive drag-and-drop interface.

Secure & Private

TLS encryption and auto-deletion keep your files safe.

Any Device

Merge PDFs on mobile, tablet, or desktop – no downloads needed.

Easy Organization

Preview, reorder, or delete pages before merging.

Cloud-Based

Save device resources with our efficient online processing.

Merge PDFs in 3 Simple Steps

  1. Upload PDFs: Click “Choose Files” or drag and drop your documents into the tool area above.
  2. Arrange Files: Drag and drop the file previews to set the perfect order for your new document.
  3. Merge & Save: Hit the “Merge PDF” button and download your combined file instantly.

Fast, Secure, and Free PDF Merging

Merge your PDF files quickly and easily into a single, professional document with AllWebToolkit. Our free online PDF merger is designed to be user-friendly, adding no watermarks and requiring no registration. Combine multiple PDFs in seconds and get a polished result every time.

Trusted by Thousands Worldwide

Join countless users who rely on our tool to combine PDFs seamlessly. Unlike other platforms like iLovePDF or Smallpdf, we offer unlimited merging without paywalls or restrictions, making us the go-to choice for students, professionals, and businesses.

More Free PDF Tools

Explore our suite of powerful, browser-based tools, all 100% free: