Solar Panel Quote Calculator: Build ROI Calculators for Solar Installers
Solar quotes involve complex calculations: energy usage analysis, system sizing, sun exposure factors, federal and state incentives, financing options, and ROI projections. Here's how to build a calculator that handles all of it in real time.
Homeowners shopping for solar want to know three things: how big a system they need, what it will cost after incentives, and how long until it pays for itself. A good calculator answers all three before they ever talk to a salesperson.
The key insight is that everything flows from energy usage. Monthly electric bill determines system size, which determines cost, which determines payback period. Get the first number right and the rest follows.
Start with Energy Usage
Most homeowners don't know their kWh consumption, but they know their electric bill. Start there and calculate backwards. A $150 bill at $0.25/kWh means roughly 600 kWh/month.
const energySection = form.addSubform('energy', {
title: 'Current Energy Usage'
});
energySection.addRow(row => {
row.addSlider('monthlyBill', {
label: 'Average Monthly Electric Bill',
min: 50,
max: 500,
step: 10,
defaultValue: 150,
unit: '$'
});
});
energySection.addRow(row => {
row.addDropdown('utilityCompany', {
label: 'Electric Utility',
options: [
{ id: 'pge', name: 'PG&E' },
{ id: 'sce', name: 'Southern California Edison' },
{ id: 'sdge', name: 'SDG&E' },
{ id: 'other', name: 'Other' }
]
});
});
// Calculate kWh from bill amount
const estimatedKwh = form.computedValue(() => {
const bill = monthlyBill.value() ?? 150;
const rate = 0.25; // Average $/kWh
return Math.round(bill / rate);
});The slider makes it easy to adjust and see how costs scale. Someone with a $300 bill sees exactly how much more their system will cost compared to someone at $150.
Pro tip
Utility rates vary significantly. If you operate in multiple markets, let users select their utility and use accurate rates for each. This improves estimate accuracy and builds trust.
Property Information
Not every property is suitable for solar. Roof type, age, and condition affect installation feasibility and cost. Old roofs may need replacement before solar installation - better to discover that early.
const propertySection = form.addSubform('property', {
title: 'Property Information'
});
propertySection.addRow(row => {
row.addDropdown('propertyType', {
label: 'Property Type',
options: [
{ id: 'single-family', name: 'Single Family Home' },
{ id: 'townhouse', name: 'Townhouse' },
{ id: 'multi-family', name: 'Multi-Family' },
{ id: 'commercial', name: 'Commercial' }
]
});
row.addDropdown('ownership', {
label: 'Ownership Status',
options: [
{ id: 'own', name: 'I own this property' },
{ id: 'buying', name: 'Buying this property' }
]
});
});
propertySection.addRow(row => {
row.addDropdown('roofType', {
label: 'Roof Type',
options: [
{ id: 'asphalt', name: 'Asphalt Shingle' },
{ id: 'tile', name: 'Tile (Clay/Concrete)' },
{ id: 'metal', name: 'Metal' },
{ id: 'flat', name: 'Flat/Low Slope' },
{ id: 'unknown', name: 'Not Sure' }
]
});
row.addInteger('roofAge', {
label: 'Roof Age (years)',
min: 0,
max: 50,
placeholder: 'Approximate age'
});
});
propertySection.addRow(row => {
row.addDropdown('roofCondition', {
label: 'Roof Condition',
options: [
{ id: 'excellent', name: 'Excellent - No issues' },
{ id: 'good', name: 'Good - Minor wear' },
{ id: 'fair', name: 'Fair - Some repairs needed' },
{ id: 'poor', name: 'Poor - Major repairs needed' }
],
isVisible: () => (roofAge.value() ?? 0) > 10
});
});The roof condition question only appears for roofs over 10 years old. Newer roofs don't need the extra question. This is conditional logic reducing friction for users who don't need to answer.
Sun Exposure Analysis
Solar production depends heavily on roof orientation and shading. A south-facing roof with no shade produces significantly more than an east-facing roof with trees. These factors directly affect system sizing and ROI.
const sunSection = form.addSubform('sun', {
title: 'Sun Exposure'
});
sunSection.addRow(row => {
row.addDropdown('roofDirection', {
label: 'Primary Roof Direction',
options: [
{ id: 'south', name: 'South-facing' },
{ id: 'southwest', name: 'Southwest-facing' },
{ id: 'southeast', name: 'Southeast-facing' },
{ id: 'west', name: 'West-facing' },
{ id: 'east', name: 'East-facing' },
{ id: 'north', name: 'North-facing' },
{ id: 'flat', name: 'Flat roof' },
{ id: 'unknown', name: 'Not sure' }
]
});
});
sunSection.addRow(row => {
row.addDropdown('shading', {
label: 'Shade on Roof',
options: [
{ id: 'none', name: 'No shade - Full sun all day' },
{ id: 'minimal', name: 'Minimal - Occasional shade' },
{ id: 'partial', name: 'Partial - Shade part of day' },
{ id: 'significant', name: 'Significant - Mostly shaded' }
]
});
});
// Sun exposure efficiency factor
const sunEfficiency = form.computedValue(() => {
const direction = roofDirection.value();
const shade = shading.value();
// Direction factor
const directionFactors: Record<string, number> = {
'south': 1.0,
'southwest': 0.95,
'southeast': 0.95,
'west': 0.85,
'east': 0.85,
'flat': 0.90,
'north': 0.65,
'unknown': 0.85
};
// Shade factor
const shadeFactors: Record<string, number> = {
'none': 1.0,
'minimal': 0.95,
'partial': 0.80,
'significant': 0.50
};
const dirFactor = directionFactors[direction ?? 'unknown'];
const shadeFactor = shadeFactors[shade ?? 'none'];
return dirFactor * shadeFactor;
});The efficiency calculation multiplies direction and shade factors. A north-facing roof with significant shade (0.65 × 0.50 = 0.325) produces only a third of what an ideal south-facing unshaded roof produces. The system needs to be three times larger to achieve the same offset.
System Sizing
With energy usage and sun exposure, you can calculate the right system size. The math: annual kWh divided by production per kW. The offset slider lets users choose how much of their usage to cover.
// Calculate system size based on usage and sun exposure
const systemSizeKw = form.computedValue(() => {
const annualKwh = estimatedKwh() * 12;
const sunHoursPerDay = 5; // Average for California
const efficiency = sunEfficiency();
// kWh per kW of solar per year
const productionRatio = sunHoursPerDay * 365 * efficiency;
// System size to offset 100% of usage
return Math.round((annualKwh / productionRatio) * 10) / 10;
});
const sizingSection = form.addSubform('sizing', {
title: 'Recommended System'
});
sizingSection.addRow(row => {
row.addSlider('offsetTarget', {
label: 'Energy Offset Target',
min: 50,
max: 120,
step: 10,
defaultValue: 100,
unit: '%'
});
});
sizingSection.addRow(row => {
row.addPriceDisplay('systemSize', {
label: 'Recommended System Size',
computedValue: () => {
const target = (offsetTarget.value() ?? 100) / 100;
return systemSizeKw() * target;
},
currency: '',
suffix: ' kW',
decimals: 1,
variant: 'highlight'
});
});Allowing offset targets above 100% is intentional. Some homeowners plan for electric vehicles or heat pumps. Others want to bank net metering credits. The calculator should accommodate future planning.
Cost Calculation with Incentives
Solar costs have three layers: gross system cost, federal tax credit, and potentially state/utility incentives. The 30% federal Investment Tax Credit (ITC) is the biggest - it reduces a $30,000 system to $21,000.
// Pricing constants
const PRICE_PER_WATT = 2.75; // Average installed cost
const FEDERAL_TAX_CREDIT = 0.30; // 30% ITC through 2032
const pricingSection = form.addSubform('pricing', {
title: 'Cost Estimate'
});
const grossCost = form.computedValue(() => {
const sizeKw = systemSizeKw() * ((offsetTarget.value() ?? 100) / 100);
return sizeKw * 1000 * PRICE_PER_WATT;
});
const federalCredit = form.computedValue(() => {
return grossCost() * FEDERAL_TAX_CREDIT;
});
const netCost = form.computedValue(() => {
return grossCost() - federalCredit();
});
pricingSection.addRow(row => {
row.addPriceDisplay('grossCost', {
label: 'System Cost (before incentives)',
computedValue: grossCost,
currency: '$',
decimals: 0
});
});
pricingSection.addRow(row => {
row.addPriceDisplay('federalCredit', {
label: 'Federal Tax Credit (30%)',
computedValue: () => -federalCredit(),
currency: '$',
decimals: 0,
variant: 'success'
});
});
pricingSection.addRow(row => {
row.addPriceDisplay('netCost', {
label: 'Net Cost After Incentives',
computedValue: netCost,
currency: '$',
decimals: 0,
variant: 'large'
});
});The price display variants help users parse the numbers quickly. Regular styling for the gross cost, green "success" for the credit (good news), and "large" for the final number they care most about.
Pro tip
Price per watt varies by market, system size, and installer. $2.75/W is a reasonable national average, but adjust for your actual pricing. The calculator is a lead generation tool, not a binding quote.
ROI Projections
Payback period is the killer metric. Homeowners want to know when solar "starts making money." Calculate it from annual savings divided by net cost, then show the 20-year projection with utility rate increases.
const roiSection = form.addSubform('roi', {
title: 'Return on Investment'
});
const annualSavings = form.computedValue(() => {
const bill = monthlyBill.value() ?? 150;
const target = (offsetTarget.value() ?? 100) / 100;
return bill * 12 * Math.min(target, 1); // Cap at 100% savings
});
const paybackYears = form.computedValue(() => {
const savings = annualSavings();
if (savings <= 0) return null;
return Math.round(netCost() / savings * 10) / 10;
});
const twentyYearSavings = form.computedValue(() => {
const savings = annualSavings();
const cost = netCost();
// Include 2% annual utility rate increase
let total = 0;
for (let year = 1; year <= 20; year++) {
total += savings * Math.pow(1.02, year);
}
return total - cost;
});
roiSection.addRow(row => {
row.addPriceDisplay('annualSavings', {
label: 'Estimated Annual Savings',
computedValue: annualSavings,
currency: '$',
decimals: 0,
suffix: '/year'
});
});
roiSection.addRow(row => {
row.addPriceDisplay('payback', {
label: 'Payback Period',
computedValue: paybackYears,
currency: '',
suffix: ' years',
decimals: 1
});
});
roiSection.addRow(row => {
row.addPriceDisplay('twentyYearSavings', {
label: '20-Year Net Savings',
computedValue: twentyYearSavings,
currency: '$',
decimals: 0,
variant: 'success'
});
});The 20-year savings calculation includes 2% annual utility rate increases. This is conservative - actual increases often exceed 3%. The compounding effect is significant: what saves $1,800 in year one saves $2,200 in year ten.
Financing Options
Most homeowners finance solar rather than paying cash. The calculator should show monthly payment options alongside the cash price. This makes solar accessible - "$150/month with no money down" is easier to process than "$25,000."
const financingSection = form.addSubform('financing', {
title: 'Financing Options'
});
financingSection.addRow(row => {
row.addRadioButton('paymentMethod', {
label: 'How would you like to pay?',
options: [
{ id: 'cash', name: 'Cash Purchase' },
{ id: 'loan', name: 'Solar Loan' },
{ id: 'lease', name: 'Solar Lease/PPA' }
],
orientation: 'vertical',
defaultValue: 'loan'
});
});
// Loan details (visible only for loan option)
financingSection.addRow(row => {
row.addDropdown('loanTerm', {
label: 'Loan Term',
options: [
{ id: '10', name: '10 Years' },
{ id: '15', name: '15 Years' },
{ id: '20', name: '20 Years' },
{ id: '25', name: '25 Years' }
],
defaultValue: '20',
isVisible: () => paymentMethod.value() === 'loan'
});
});
const monthlyPayment = form.computedValue(() => {
if (paymentMethod.value() !== 'loan') return null;
const principal = netCost();
const rate = 0.069 / 12; // 6.9% APR
const term = parseInt(loanTerm.value() ?? '20') * 12;
return (principal * rate * Math.pow(1 + rate, term)) /
(Math.pow(1 + rate, term) - 1);
});
financingSection.addRow(row => {
row.addPriceDisplay('monthlyPayment', {
label: 'Estimated Monthly Payment',
computedValue: monthlyPayment,
currency: '$',
decimals: 0,
suffix: '/month',
isVisible: () => paymentMethod.value() === 'loan'
});
});The loan payment calculation uses standard amortization. Different loan terms create different monthly payments - a 25-year loan has lower payments but more total interest. Let users explore the tradeoffs.
Environmental Impact
Some buyers are motivated by environmental benefits as much as financial. Show the carbon offset and equivalent metrics to appeal to this segment.
const impactSection = form.addSubform('impact', {
title: 'Environmental Impact'
});
const annualCo2Offset = form.computedValue(() => {
const kwhProduced = systemSizeKw() * 5 * 365 * sunEfficiency();
const lbsCo2PerKwh = 0.92; // EPA average
return Math.round(kwhProduced * lbsCo2PerKwh);
});
impactSection.addRow(row => {
row.addPriceDisplay('co2Offset', {
label: 'Annual CO2 Offset',
computedValue: annualCo2Offset,
currency: '',
suffix: ' lbs',
decimals: 0
});
});
impactSection.addRow(row => {
row.addPriceDisplay('treesEquivalent', {
label: 'Equivalent Trees Planted',
computedValue: () => Math.round(annualCo2Offset() / 48),
currency: '',
suffix: ' trees/year',
decimals: 0
});
});Converting CO2 to "trees equivalent" makes the number tangible. 500 trees per year is more meaningful to most people than 24,000 lbs of carbon.
Lead Capture
The calculator's purpose is lead generation. After showing all the numbers, collect contact information for sales follow-up.
const contactSection = form.addSubform('contact', {
title: 'Get Your Free Quote'
});
contactSection.addRow(row => {
row.addTextbox('fullName', {
label: 'Full Name',
isRequired: true
});
row.addEmail('email', {
label: 'Email',
isRequired: true
});
});
contactSection.addRow(row => {
row.addTextbox('phone', {
label: 'Phone',
isRequired: true
});
row.addAddress('address', {
label: 'Property Address',
isRequired: true,
restrictToCountries: ['us']
});
});
contactSection.addRow(row => {
row.addDropdown('timeline', {
label: 'When are you looking to go solar?',
options: [
{ id: '1-month', name: 'Within 1 month' },
{ id: '3-months', name: 'Within 3 months' },
{ id: '6-months', name: 'Within 6 months' },
{ id: 'researching', name: 'Just researching' }
]
});
});The address field is particularly valuable - it lets you check service area and look up the property for satellite-based system design before the first call.
See more quote calculator examples in our gallery.
Putting It Together
A complete solar calculator flows logically: energy usage determines system size, property details affect feasibility, sun exposure refines the estimate, costs include incentives, ROI shows the payback, and financing makes it affordable. Each section builds on the previous.
The reactive calculations mean users can experiment. "What if I size up for an EV?" Change the offset to 130% and watch the numbers update. "What if I pay cash vs finance?" Toggle the option and compare. This interactivity builds engagement and qualified leads.
Common Questions
How accurate should the system size estimate be?
Aim for ±15% of what a site survey would determine. The calculator can't account for exact roof dimensions, electrical panel capacity, or local permitting requirements. Position it as a preliminary estimate that helps homeowners understand the general scope.
Should I include battery storage pricing?
If you sell batteries, yes. Add a checkbox for 'Include battery backup' with capacity options (10kWh, 20kWh). Battery pricing adds $10-15k to the system cost but provides backup power and may improve economics in time-of-use rate markets.
How do I handle different state incentives?
Use the address field to determine state, then apply state-specific credits. Some states offer significant additional incentives (New York, Massachusetts, California). Store incentive data in a lookup table and apply based on location.
What about commercial solar calculations?
Commercial solar is different - larger systems, different incentives (depreciation, RECs), and more complex utility rate structures. Build a separate calculator for commercial prospects rather than trying to handle both in one form.