How to Build a Renovation Cost Calculator with the GreenCalc API
A complete tutorial: build a web-based calculator that takes a user’s renovation plans and shows estimated grants, loans, tax savings, and out-of-pocket costs — using plain HTML, CSS, and JavaScript.
Renovation cost calculators are among the highest-converting widgets for energy advisor websites, contractor platforms, and green finance apps. Users type in their project details and instantly see how much government funding they can access. In this tutorial, we build one from scratch using the GreenCalc API and vanilla JavaScript — no frameworks required.
What we are building
Our calculator will have three sections:
- Input form: Country, income, household size, postal code, property type, and a list of planned works with costs.
- Results panel: Breakdown of grants, loans, tax savings, and net out-of-pocket cost.
- Scheme details: Expandable list showing each eligible subsidy with its name, type, and amount.
The entire thing is a single HTML file. You can drop it into any website, embed it in an iframe, or adapt it to your framework of choice.
Step 1: The HTML form
Start with a clean form that collects the minimum data needed by the API. Note the use of data- attributes to store the normalized work type codes:
<!-- renovation-calculator.html --> <form id="calc-form"> <div class="form-row"> <label>Country <select name="country" required> <option value="FR">France</option> <option value="DE">Germany</option> <option value="IT">Italy</option> <option value="BE">Belgium</option> <option value="ES">Spain</option> </select> </label> <label>Annual Income (€) <input type="number" name="income" value="35000" required> </label> <label>Household Size <input type="number" name="household_size" value="3" min="1" required> </label> </div> <div class="form-row"> <label>Postal Code <input type="text" name="postal_code" value="75012" required> </label> <label>Property Type <select name="property_type"> <option value="HOUSE">House</option> <option value="APARTMENT">Apartment</option> </select> </label> <label>Surface (m²) <input type="number" name="surface" value="100"> </label> </div> <!-- Work items added dynamically --> <div id="works-list"></div> <button type="button" id="add-work">+ Add work</button> <button type="submit">Calculate Subsidies</button> </form> <div id="results" hidden></div>
Step 2: The JavaScript API call
The core function builds the request payload from form data and calls the GreenCalc API. For prototyping, we use the sandbox key directly. In production, you would proxy this through your backend:
const API_URL = 'https://greencalc.io/api/v1/eligibility/simulate'; const API_KEY = 'gc_sandbox_000000000000000000000000000000000'; async function calculateSubsidies(formData) { const works = collectWorks(); // reads from dynamic work list const body = { country_code: formData.get('country'), household: { annual_income: Number(formData.get('income')), household_size: Number(formData.get('household_size')), is_owner: true }, property: { type: formData.get('property_type'), postal_code: formData.get('postal_code'), surface_m2: Number(formData.get('surface')) }, planned_works: works }; const res = await fetch(API_URL, { method: 'POST', headers: { 'X-Api-Key': API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify(body) }); if (!res.ok) throw new Error(`API error: ${res.status}`); return await res.json(); }
Step 3: The work item collector
Each work item has a type dropdown and a cost input. The collectWorks function reads all work rows:
const WORK_TYPES = [ { code: 'ROOF_INSULATION', label: 'Roof insulation', hasSurface: true }, { code: 'WALL_INSULATION_EXTERIOR', label: 'Wall insulation (exterior)', hasSurface: true }, { code: 'HEAT_PUMP_AIR_WATER', label: 'Heat pump (air-water)', hasSurface: false }, { code: 'HEAT_PUMP_GEOTHERMAL', label: 'Heat pump (geothermal)', hasSurface: false }, { code: 'WINDOWS_DOUBLE_GLAZING', label: 'Double-glazing windows', hasSurface: false }, { code: 'WINDOWS_TRIPLE_GLAZING', label: 'Triple-glazing windows', hasSurface: false }, { code: 'SOLAR_PANELS_PHOTOVOLTAIC', label: 'Solar panels (PV)', hasSurface: false }, { code: 'SOLAR_THERMAL', label: 'Solar thermal', hasSurface: false }, { code: 'CONDENSING_BOILER', label: 'Condensing boiler', hasSurface: false }, { code: 'FLOOR_INSULATION', label: 'Floor insulation', hasSurface: true } ]; function collectWorks() { const rows = document.querySelectorAll('.work-row'); return Array.from(rows).map(row => { const work = { work_type: row.querySelector('[name="work_type"]').value, estimated_cost_eur: Number(row.querySelector('[name="cost"]').value) }; const surfaceInput = row.querySelector('[name="work_surface"]'); if (surfaceInput && surfaceInput.value) { work.surface_m2 = Number(surfaceInput.value); } return work; }); }
Step 4: Rendering the results
The result renderer takes the API response and builds a clear breakdown. The key is to separate grants (money you get), loans (money you borrow at 0%), and tax savings (money you save later):
function renderResults(data) { const el = document.getElementById('results'); const { summary, eligible_subsidies } = data; el.innerHTML = ` <div class="summary-card"> <div class="cost-row"> <span>Total renovation cost</span> <strong>€${fmt(summary.total_estimated_cost_eur)}</strong> </div> <div class="cost-row grant"> <span>Grants you receive</span> <strong>-€${fmt(summary.total_grants_eur)}</strong> </div> <div class="cost-row tax"> <span>Tax savings</span> <strong>-€${fmt(summary.total_tax_savings_eur)}</strong> </div> <div class="cost-row total"> <span>Your out-of-pocket cost</span> <strong>€${fmt(summary.estimated_out_of_pocket_eur)}</strong> </div> ${summary.total_loans_eur > 0 ? ` <div class="cost-row loan"> <span>0% government loan available</span> <strong>€${fmt(summary.total_loans_eur)}</strong> </div>` : ''} </div> <h3>Eligible subsidies</h3> ${eligible_subsidies.map(s => ` <div class="scheme-card"> <strong>${s.scheme_name}</strong> <span class="badge ${s.type.toLowerCase()}">${s.type}</span> <span class="amount">€${fmt(s.amount_eur)}</span> </div> `).join('')} `; el.hidden = false; } function fmt(n) { return n.toLocaleString('en', { maximumFractionDigits: 0 }); }
Step 5: Wiring it all together
Add the event listener and the dynamic work row builder:
document.getElementById('calc-form').addEventListener('submit', async (e) => { e.preventDefault(); const btn = e.target.querySelector('[type="submit"]'); btn.textContent = 'Calculating...'; btn.disabled = true; try { const data = await calculateSubsidies(new FormData(e.target)); renderResults(data); } catch (err) { document.getElementById('results').innerHTML = `<p class="error">Calculation failed: ${err.message}</p>`; document.getElementById('results').hidden = false; } finally { btn.textContent = 'Calculate Subsidies'; btn.disabled = false; } }); document.getElementById('add-work').addEventListener('click', () => { const row = document.createElement('div'); row.className = 'work-row'; row.innerHTML = ` <select name="work_type"> ${WORK_TYPES.map(w => \`<option value="\${w.code}">\${w.label}</option>\` ).join('')} </select> <input name="cost" type="number" placeholder="Cost (€)" required> <input name="work_surface" type="number" placeholder="m² (if applicable)"> <button type="button" onclick="this.parentElement.remove()">×</button> `; document.getElementById('works-list').appendChild(row); });
Testing with curl
Before building the UI, test the API directly to understand the response format:
curl -X POST https://greencalc.io/api/v1/eligibility/simulate \ -H "X-Api-Key: gc_sandbox_000000000000000000000000000000000" \ -H "Content-Type: application/json" \ -d '{ "country_code": "FR", "household": { "annual_income": 28000, "household_size": 2, "is_owner": true }, "property": { "type": "APARTMENT", "postal_code": "33000", "surface_m2": 65 }, "planned_works": [ {"work_type": "WINDOWS_DOUBLE_GLAZING", "estimated_cost_eur": 5500} ] }'
Styling tips
The calculator works with any CSS framework. Here are design patterns that perform well in user testing:
- Use a progress bar showing the ratio of “government pays” vs. “you pay” — visually compelling and immediately understood.
- Color-code subsidy types — green for grants (free money), blue for loans (must repay), amber for tax savings (recovered later). Users need to understand the difference.
- Show monthly cost if a loan is involved. “€13,900 over 10 years = €116/month” is more relatable than a lump sum for most homeowners.
- Add country flags next to the country selector. It helps with quick recognition on multi-country platforms.
- Show a disclaimer that amounts are estimates based on current rates. Link to the official government portal for each country.
POST /api/calculate-subsidies on your server that forwards the request to GreenCalc with your live API key. This keeps the key secure and lets you add caching, logging, and rate limiting.
Extending the calculator
Once the basic calculator works, consider these enhancements:
- Country comparison mode: run the same project through all 5 countries simultaneously and display a comparison table. Useful for expats or people choosing where to buy property. See our country comparison article for the data.
- PDF export: generate a PDF summary of the simulation. Useful for energy advisors who need to print results for clients.
- Contractor integration: embed the calculator on contractor websites with pre-filled work types and average costs per region.
- Green loan integration: add a “finance the rest” button that connects to a lending partner. See our green loan calculator tutorial for this workflow.
The full API reference with all supported fields is at greencalc.io/docs.
Frequently Asked Questions
Can I call the GreenCalc API from a browser (client-side JavaScript)?
The sandbox key can be used from the browser for prototyping and demos. For production, proxy API calls through your backend to protect your live API key. Live keys should never be exposed in client-side code.
What work types does the GreenCalc API support?
The API supports 11 normalized work types: ROOF_INSULATION, WALL_INSULATION_EXTERIOR, WALL_INSULATION_INTERIOR, FLOOR_INSULATION, WINDOWS_DOUBLE_GLAZING, WINDOWS_TRIPLE_GLAZING, HEAT_PUMP_AIR_WATER, HEAT_PUMP_GEOTHERMAL, CONDENSING_BOILER, SOLAR_PANELS_PHOTOVOLTAIC, and SOLAR_THERMAL. These codes work across all 5 supported countries.
How accurate are the cost estimates from the calculator?
The GreenCalc API calculates subsidy amounts based on official rates. The renovation cost itself (estimated_cost_eur) is provided by the user. For more accurate costs, pair the subsidy calculation with contractor quotes or average market prices per work type and region.
GreenCalc is built by AZMORIS Group. Full API documentation at greencalc.io/docs.