Auto Loan Calculator

Professional auto loan calculator for car buyers and planners. Estimate monthly payment, amount financed, total interest, sales tax and fees impact, and total cost.

Calculator mode

Enter a fixed amount or a percentage of the vehicle price depending on the selected option. Current unit: $.

If checked, fees are included when computing the tax base.

Choose whether the number above is in months or years.

How to use this calculator

Start in the payment mode to quickly estimate your monthly payment, total interest, and payoff time. Switch to affordability mode when you know what payment fits your budget and want to see the maximum vehicle price you can finance. Enter the vehicle price, trade-in, down payment (amount or percent), applicable sales tax and fees, APR, term, and any extra monthly principal.

Taxation options let you choose whether the tax base includes the down payment and whether fees are taxable. The calculator simulates each monthly payment and updates results after you click Calculate or when inputs change.

Methodology

This tool uses the fixed-rate amortization formula: the loan balance is paid down with equal principal-and-interest payments each period. Extra monthly payments, when applied, reduce the remaining balance and shorten the payoff time. Sales tax is applied to the chosen tax base (price minus trade-in, optionally minus down payment) and is included in the financed principal.

  • Payment mode calculates the monthly payment necessary to retire the financed amount over your chosen term.
  • Affordability mode computes how much financing you can support based on a target monthly payment.
  • Schedule and CSV export reflect the aggregate payment (base payment + extra principal) for each period.

Results are estimates for planning purposes. Actual offers differ due to lender fees, rounding, and regional tax rules. Review the schedule to understand how extra payments affect interest and payoff time.

About the author

Ugo Candido builds financial tools and educational resources so readers can make better decisions with transparent, practical models.

Contact: info@calcdomain.com

Editorial policy

CalcDomain content is created for education and transparency. Inputs and assumptions are shown directly in the interface. We do not accept paid placements that influence calculator outputs.

Formulas

Monthly payment (amortizing loan):

PMT = P × i / (1 - (1 + i)^-n)

Amount financed:

P = (Price - Down - TradeIn) + Fees + Tax_sales

Taxable base: price minus trade-in (optionally also minus down payment) plus taxable fees when the checkbox is selected.

Citations

OpenStax, “Business Math,” Section 13.3 Annuities—Present Value and Payments (2022). openstax.org.

All calculations strictly follow the formulas and assumptions described by this source.

Changelog
  • 0.1.0-draft — 2026-01-19: Initial audit specification draft generated from HTML extraction.
  • Audit checklist: verify formulas match the engine and convert text formulas to LaTeX.
  • Confirm listed sources remain authoritative and relevant.
Verified by Ugo Candido Last Updated: 2026-01-19 Version 0.1.0-draft
Version 1.5.0
+ round2(safe).toFixed(2); } }; const payoffLabel = (months) => { if (!months || months <= 0) return '—'; const years = Math.floor(months / 12); const rem = months % 12; if (years === 0) return `${months} months`; if (rem === 0) return `${years} years`; return `${years} yrs ${rem} mos`; }; const els = { modePayment: document.getElementById('modePayment'), modeAffordability: document.getElementById('modeAffordability'), vehiclePriceInput: document.getElementById('vehiclePriceInput'), monthlyPaymentInput: document.getElementById('monthlyPaymentInput'), vehiclePrice: document.getElementById('vehiclePrice'), tradeInValue: document.getElementById('tradeInValue'), monthlyPayment: document.getElementById('monthlyPayment'), downPayment: document.getElementById('downPayment'), downAmountRadio: document.getElementById('downAmountRadio'), downPercentRadio: document.getElementById('downPercentRadio'), downUnit: document.getElementById('downUnit'), salesTaxRate: document.getElementById('salesTaxRate'), fees: document.getElementById('fees'), feesTaxed: document.getElementById('feesTaxed'), taxBase: document.getElementById('taxBase'), apr: document.getElementById('apr'), aprAnalysis: document.getElementById('aprAnalysis'), loanTerm: document.getElementById('loanTerm'), termMonthsRadio: document.getElementById('termMonthsRadio'), termYearsRadio: document.getElementById('termYearsRadio'), extraPayment: document.getElementById('extraPayment'), calcBtn: document.getElementById('calcBtn'), resetBtn: document.getElementById('resetBtn'), errorBox: document.getElementById('errorBox'), primaryLabel: document.getElementById('primaryLabel'), primaryValue: document.getElementById('primaryValue'), resultAmountFinanced: document.getElementById('resultAmountFinanced'), resultSalesTax: document.getElementById('resultSalesTax'), resultTotalInterest: document.getElementById('resultTotalInterest'), resultPayoffTerm: document.getElementById('resultPayoffTerm'), resultVehiclePrice: document.getElementById('resultVehiclePrice'), toggleSchedule: document.getElementById('toggleSchedule'), downloadCsv: document.getElementById('downloadCsv'), copyLinkBtn: document.getElementById('copyLinkBtn'), scheduleWrap: document.getElementById('scheduleWrap'), scheduleBody: document.getElementById('scheduleBody') }; let currentMode = 'payment'; let currentSchedule = []; let scheduleVisible = false; let copyTimeout; const debounce = (fn, delay = 100) => { let timer; return (...args) => { clearTimeout(timer); timer = setTimeout(() => fn(...args), delay); }; }; const syncToggleLabels = () => { document.querySelectorAll('.toggle-label').forEach((label) => { const input = label.querySelector('input.toggle-input'); if (!input) return; label.classList.toggle('active', input.checked); }); }; const defaults = () => ({ vehiclePrice: 30000, tradeIn: 5000, monthlyPayment: 500, downPayment: 3000, salesTaxRate: 7.5, fees: 500, apr: 5.5, loanTerm: 60, extraPayment: 0, downMode: 'amount', termMode: 'months', taxBase: 'priceMinusTrade', feesTaxed: false }); const setMode = (mode) => { currentMode = mode; const isPayment = mode === 'payment'; els.vehiclePriceInput.classList.toggle('hidden', !isPayment); els.monthlyPaymentInput.classList.toggle('hidden', isPayment); els.modePayment.checked = isPayment; els.modeAffordability.checked = !isPayment; syncToggleLabels(); if (!isPayment) { scheduleVisible = false; els.scheduleWrap.style.display = 'none'; els.toggleSchedule.textContent = 'View Amortization Schedule'; els.toggleSchedule.disabled = true; els.downloadCsv.disabled = true; } }; function parseInputs() { const vehiclePrice = toNumber(els.vehiclePrice.value); const tradeIn = toNumber(els.tradeInValue.value); const rawDown = toNumber(els.downPayment.value); const downPayment = els.downPercentRadio.checked && vehiclePrice > 0 ? vehiclePrice * (rawDown / 100) : rawDown; const salesTaxRate = Math.max(0, toNumber(els.salesTaxRate.value)) / 100; const fees = Math.max(0, toNumber(els.fees.value)); const aprPct = Math.max(0, toNumber(els.apr.value)); const termRaw = Math.max(0, toNumber(els.loanTerm.value)); const termMonths = els.termYearsRadio.checked ? termRaw * 12 : termRaw; const extraPayment = Math.max(0, toNumber(els.extraPayment.value)); const monthlyPayment = Math.max(0, toNumber(els.monthlyPayment.value)); return { mode: currentMode, vehiclePrice, tradeIn, downPayment: Math.max(0, downPayment), salesTaxRate, fees, aprPct, termMonths: Math.round(Math.max(0, termMonths)), extraPayment, monthlyPayment, monthlyMode: els.downPercentRadio.checked ? 'percent' : 'amount', termMode: els.termYearsRadio.checked ? 'years' : 'months', taxBase: els.taxBase.value, feesTaxed: els.feesTaxed.checked }; } function validate(inputs) { const errors = []; if (inputs.vehiclePrice <= 0 && inputs.mode === 'payment') errors.push('Vehicle price must be greater than 0.'); if (inputs.termMonths <= 0) errors.push('Loan term must be greater than 0.'); if (inputs.aprPct < 0) errors.push('APR must be 0 or greater.'); if (inputs.salesTaxRate < 0) errors.push('Sales tax rate must be 0 or greater.'); if (inputs.fees < 0) errors.push('Fees must be 0 or greater.'); if (inputs.extraPayment < 0) errors.push('Extra payment must be 0 or greater.'); if (inputs.mode === 'affordability' && inputs.monthlyPayment <= 0) errors.push('Monthly payment must be greater than 0.'); const validBases = new Set(['priceMinusTrade', 'priceMinusTradeDown']); if (!validBases.has(inputs.taxBase)) errors.push('Tax base selection is invalid.'); return { ok: errors.length === 0, errors }; } function compute(inputs) { if (inputs.mode === 'payment') return computePayment(inputs); return computeAffordability(inputs); } function buildTaxableBase(price, inputs, down) { let base = price - inputs.tradeIn; if (inputs.taxBase === 'priceMinusTradeDown') base -= down; if (inputs.feesTaxed) base += inputs.fees; return Math.max(0, base); } function computePayment(inputs) { const taxableBase = buildTaxableBase(inputs.vehiclePrice, inputs, inputs.downPayment); const salesTax = round2(taxableBase * inputs.salesTaxRate); const principal = inputs.vehiclePrice - inputs.downPayment - inputs.tradeIn + inputs.fees + salesTax; if (!(principal > 0)) return null; const monthlyRate = (inputs.aprPct / 100) / 12; const n = inputs.termMonths; if (n <= 0) return null; let basePayment; if (monthlyRate === 0) basePayment = principal / n; else basePayment = (principal * monthlyRate * Math.pow(1 + monthlyRate, n)) / (Math.pow(1 + monthlyRate, n) - 1); if (!Number.isFinite(basePayment) || basePayment <= 0) return null; const schedule = []; let balance = principal; let months = 0; let totalInterestPaid = 0; const MAX_PERIODS = 600; while (balance > 0.005 && months < MAX_PERIODS) { months += 1; const interest = balance * monthlyRate; let principalPaid = basePayment - interest; if (principalPaid < 0) break; let extra = inputs.extraPayment; if (principalPaid + extra > balance) { extra = Math.max(0, balance - principalPaid); principalPaid = Math.max(0, balance - extra); } if (principalPaid > balance) principalPaid = balance; balance = Math.max(0, balance - principalPaid - extra); totalInterestPaid += interest; const totalPayment = round2(principalPaid + interest + extra); schedule.push({ month: months, payment: totalPayment, interest: round2(interest), principal: round2(principalPaid), extra: round2(extra), balance: round2(balance) }); if (balance <= 0) break; } return { mode: 'payment', monthlyPayment: round2(basePayment), totalInterest: round2(totalInterestPaid), payoffMonths: months, schedule, amountFinanced: round2(principal), salesTax, fees: round2(inputs.fees) }; } function computeAffordability(inputs) { const monthlyRate = (inputs.aprPct / 100) / 12; const n = inputs.termMonths; if (!(n > 0) || !(inputs.monthlyPayment > 0)) return null; let amountFinanced; if (monthlyRate === 0) amountFinanced = inputs.monthlyPayment * n; else amountFinanced = (inputs.monthlyPayment * (Math.pow(1 + monthlyRate, n) - 1)) / (monthlyRate * Math.pow(1 + monthlyRate, n)); if (!Number.isFinite(amountFinanced)) return null; const A = inputs.salesTaxRate; const d = inputs.downPayment; const t = inputs.tradeIn; const fees = inputs.fees; const includeDown = inputs.taxBase === 'priceMinusTradeDown'; const feeTaxAdjustment = inputs.feesTaxed ? fees : 0; const numerator = amountFinanced + d + t - fees + A * (t + (includeDown ? d : 0)) - A * feeTaxAdjustment; const denominator = 1 + A; if (denominator === 0) return null; const vehiclePrice = Math.max(0, numerator / denominator); const taxableBase = buildTaxableBase(vehiclePrice, inputs, d); const salesTax = round2(taxableBase * A); return { mode: 'affordability', affordablePrice: round2(vehiclePrice), amountFinanced: round2(amountFinanced), totalInterest: round2((inputs.monthlyPayment * n) - amountFinanced), payoffMonths: n, schedule: [], salesTax }; } function format(result, inputs) { if (!result) return {}; const formatted = { primaryLabel: result.mode === 'payment' ? 'Monthly Payment' : 'Affordable price', primaryValue: result.mode === 'payment' ? fmtCurrency(result.monthlyPayment) : fmtCurrency(result.affordablePrice), amountFinanced: fmtCurrency(result.amountFinanced), salesTax: fmtCurrency(result.salesTax), totalInterest: fmtCurrency(result.totalInterest), payoffTerm: payoffLabel(result.payoffMonths), vehiclePrice: result.mode === 'affordability' ? fmtCurrency(result.affordablePrice) : '—' }; return formatted; } function renderErrors(errors) { if (!errors || errors.length === 0) { els.errorBox.style.display = 'none'; els.errorBox.textContent = ''; return; } els.errorBox.style.display = 'block'; els.errorBox.textContent = errors.join(' '); } function renderScheduleTable() { els.scheduleBody.innerHTML = ''; const rows = currentSchedule.slice(0, 360); const frag = document.createDocumentFragment(); rows.forEach((row) => { const tr = document.createElement('tr'); tr.innerHTML = ` ${row.month} ${fmtCurrency(row.payment)} ${fmtCurrency(row.interest)} ${fmtCurrency(row.principal)} ${fmtCurrency(row.extra)} ${fmtCurrency(row.balance)} `; frag.appendChild(tr); }); els.scheduleBody.appendChild(frag); } function render(formatted, errors) { renderErrors(errors); if (!formatted || !formatted.primaryValue) { els.primaryLabel.textContent = 'Monthly Payment'; els.primaryValue.textContent = '$0.00'; els.resultAmountFinanced.textContent = '$0.00'; els.resultSalesTax.textContent = '$0.00'; els.resultTotalInterest.textContent = '$0.00'; els.resultPayoffTerm.textContent = '—'; els.resultVehiclePrice.textContent = '—'; els.toggleSchedule.disabled = true; els.downloadCsv.disabled = true; currentSchedule = []; scheduleVisible = false; els.scheduleWrap.style.display = 'none'; els.toggleSchedule.textContent = 'View Amortization Schedule'; els.primaryLabel.textContent = 'Monthly Payment'; return; } els.primaryLabel.textContent = formatted.primaryLabel; els.primaryValue.textContent = formatted.primaryValue; els.resultAmountFinanced.textContent = formatted.amountFinanced; els.resultSalesTax.textContent = formatted.salesTax; els.resultTotalInterest.textContent = formatted.totalInterest; els.resultPayoffTerm.textContent = formatted.payoffTerm; els.resultVehiclePrice.textContent = formatted.vehiclePrice || '—'; const hasSchedule = currentSchedule.length > 0; els.toggleSchedule.disabled = !hasSchedule; els.downloadCsv.disabled = !hasSchedule; if (!hasSchedule) { scheduleVisible = false; els.scheduleWrap.style.display = 'none'; els.toggleSchedule.textContent = 'View Amortization Schedule'; } } function updateAprAnalysis(aprValue) { let label = 'APR rating: '; let tone = 'text-gray-600'; if (aprValue < 5) { label += 'Excellent'; tone = 'text-green-600'; } else if (aprValue < 8) { label += 'Good'; tone = 'text-lime-600'; } else if (aprValue < 13) { label += 'Fair'; tone = 'text-yellow-600'; } else { label += 'High'; tone = 'text-red-600'; } els.aprAnalysis.textContent = label; els.aprAnalysis.className = `text-xs mt-1 h-4 ${tone}`; } function handleToggleSchedule() { if (!currentSchedule.length) return; scheduleVisible = !scheduleVisible; els.scheduleWrap.style.display = scheduleVisible ? 'block' : 'none'; els.toggleSchedule.textContent = scheduleVisible ? 'Hide Amortization Schedule' : 'View Amortization Schedule'; if (scheduleVisible) { renderScheduleTable(); } } function downloadCsvHandler() { if (!currentSchedule.length) return; let csv = '#,Total Payment,Interest,Principal,Extra,Balance\n'; currentSchedule.forEach((row) => { csv += `${row.month},${row.payment},${row.interest},${row.principal},${row.extra},${row.balance}\n`; }); const blob = new Blob([csv], { type: 'text/csv' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'amortization-schedule.csv'; a.click(); URL.revokeObjectURL(url); } function buildShareUrl(inputs) { const params = new URLSearchParams(); params.set('mode', inputs.mode); params.set('price', inputs.vehiclePrice.toString()); params.set('trade', inputs.tradeIn.toString()); params.set('down', inputs.downPayment.toString()); params.set('downMode', inputs.monthlyMode); params.set('taxRate', (inputs.salesTaxRate * 100).toString()); params.set('fees', inputs.fees.toString()); params.set('feesTaxed', inputs.feesTaxed ? '1' : '0'); params.set('taxBase', inputs.taxBase); params.set('apr', inputs.aprPct.toString()); params.set('term', inputs.termMonths.toString()); params.set('termMode', inputs.termMode); params.set('extra', inputs.extraPayment.toString()); if (inputs.mode === 'affordability') { params.set('monthlyPayment', inputs.monthlyPayment.toString()); } const base = `${window.location.origin}${window.location.pathname}`; return `${base}?${params.toString()}`; } function copyLink() { const inputs = parseInputs(); const url = buildShareUrl(inputs); const write = (text) => { if (navigator.clipboard && navigator.clipboard.writeText) { return navigator.clipboard.writeText(text); } const textarea = document.createElement('textarea'); textarea.value = text; textarea.style.position = 'fixed'; textarea.style.opacity = '0'; document.body.appendChild(textarea); textarea.select(); document.execCommand('copy'); document.body.removeChild(textarea); return Promise.resolve(); }; write(url).then(() => { els.copyLinkBtn.textContent = 'Link copied!'; clearTimeout(copyTimeout); copyTimeout = setTimeout(() => { els.copyLinkBtn.textContent = 'Copy Link'; }, 1500); }).catch(() => { els.copyLinkBtn.textContent = 'Copy failed'; clearTimeout(copyTimeout); copyTimeout = setTimeout(() => { els.copyLinkBtn.textContent = 'Copy Link'; }, 1500); }); } function update() { const inputs = parseInputs(); const validation = validate(inputs); if (!validation.ok) { currentSchedule = []; render({}, validation.errors); return; } const result = compute(inputs); if (!result) { render({}, ['Calculation failed for the current inputs. Please revise values.']); return; } currentSchedule = Array.isArray(result.schedule) ? result.schedule : []; scheduleVisible = false; els.scheduleWrap.style.display = 'none'; els.toggleSchedule.textContent = 'View Amortization Schedule'; if (!currentSchedule.length) { els.toggleSchedule.disabled = true; els.downloadCsv.disabled = true; scheduleVisible = false; els.scheduleWrap.style.display = 'none'; els.toggleSchedule.textContent = 'View Amortization Schedule'; } const formatted = format(result, inputs); render(formatted, []); updateAprAnalysis(inputs.aprPct); } els.modePayment.addEventListener('click', () => { setMode('payment'); update(); }); els.modeAffordability.addEventListener('click', () => { setMode('affordability'); update(); }); els.downAmountRadio.addEventListener('click', () => { syncToggleLabels(); els.downUnit.textContent = '; update(); }); els.downPercentRadio.addEventListener('click', () => { syncToggleLabels(); els.downUnit.textContent = '%'; update(); }); els.termMonthsRadio.addEventListener('click', () => { syncToggleLabels(); update(); }); els.termYearsRadio.addEventListener('click', () => { syncToggleLabels(); update(); }); el.addEventListener('change', debouncedUpdate); }); els.calcBtn.addEventListener('click', update); els.resetBtn.addEventListener('click', () => { const def = defaults(); els.vehiclePrice.value = def.vehiclePrice; els.tradeInValue.value = def.tradeIn; els.monthlyPayment.value = def.monthlyPayment; els.downPayment.value = def.downPayment; els.salesTaxRate.value = def.salesTaxRate; els.fees.value = def.fees; els.feesTaxed.checked = def.feesTaxed; els.taxBase.value = def.taxBase; els.apr.value = def.apr; els.loanTerm.value = def.loanTerm; els.extraPayment.value = def.extraPayment; els.downAmountRadio.checked = true; els.downPercentRadio.checked = false; els.termMonthsRadio.checked = true; els.termYearsRadio.checked = false; setMode('payment'); els.downUnit.textContent = '; syncToggleLabels(); const debouncedUpdate = debounce(update, 100); document.querySelectorAll('#inputsCard input, #inputsCard select, #inputsCard textarea') .forEach((el) => { el.addEventListener('input', debouncedUpdate); el.addEventListener('change', debouncedUpdate); }); update(); }); els.toggleSchedule.addEventListener('click', handleToggleSchedule); els.downloadCsv.addEventListener('click', downloadCsvHandler); els.copyLinkBtn.addEventListener('click', copyLink); setMode('payment'); syncToggleLabels(); update(); })();