Most Scope 2 carbon calculators I've reviewed do one of two things: hardcode a single global average factor and call it a day, or reach for a third-party API that charges per lookup and introduces a network dependency into a calculation that should be instant and deterministic.
Neither is acceptable if you're building tools that practitioners will use to file regulatory disclosures.
Here's how I structure the grid emission factor lookup at GreenCalculus.com, using a flat data object sourced from IEA 2026, DEFRA 2025, and EPA eGRID 2024 — with zero runtime dependencies.
Why grid factors are harder than they look
The GHG Protocol Scope 2 Guidance requires companies to report two methods wherever data is available:
- Location-based: average grid emission intensity for the country or subregion where electricity is consumed (kWh × grid factor = kgCO₂e)
- Market-based: factor from a specific electricity contract, renewable energy certificate (EAC/REGO), or supplier disclosure
Location-based is what you can build a deterministic lookup for. Market-based requires data the user must supply. A well-built calculator handles both paths, makes the distinction visible, and never conflates them.
Second problem: a single national average is often wrong. The US national average from EPA eGRID is 0.386 kg CO₂e/kWh. But a data centre in Upstate New York (hydro + nuclear dominated) sits at 0.125 kg CO₂e/kWh — nearly a 3× difference. Using the national average here would overstate Scope 2 by 200%. For CSRD filers, that's a material error.
The data structure
I maintain all factors in a single PHP object (the "Master Brain") that gets injected into window.gcMasterBrain server-side. Every calculator reads from window.gcMasterBrain.grid[countryCode].factor. No hardcoded numbers anywhere in calculator JS.
Here's the shape of each entry:
// window.gcMasterBrain.grid (subset shown)
const gridFactors = {
// Europe
GB: { factor: 0.177, unit: "kg CO2e per kWh", source: "DEFRA_2025", year: 2025, name: "United Kingdom", note: "DEFRA 2025. -15% vs 2024." },
DE: { factor: 0.364, unit: "kg CO2e per kWh", source: "IEA_2026", year: 2026, name: "Germany" },
FR: { factor: 0.052, unit: "kg CO2e per kWh", source: "IEA_2026", year: 2026, name: "France", note: "~70% nuclear" },
PL: { factor: 0.635, unit: "kg CO2e per kWh", source: "IEA_2026", year: 2026, name: "Poland", note: "Coal-heavy mix" },
SE: { factor: 0.013, unit: "kg CO2e per kWh", source: "IEA_2026", year: 2026, name: "Sweden", note: "Hydro + nuclear" },
NO: { factor: 0.009, unit: "kg CO2e per kWh", source: "IEA_2026", year: 2026, name: "Norway", note: "~99% hydropower" },
// Asia-Pacific
IN: { factor: 0.708, unit: "kg CO2e per kWh", source: "IEA_2026", year: 2026, name: "India" },
CN: { factor: 0.581, unit: "kg CO2e per kWh", source: "IEA_2026", year: 2026, name: "China", note: "Declining as solar/wind scales" },
JP: { factor: 0.453, unit: "kg CO2e per kWh", source: "IEA_2026", year: 2026, name: "Japan" },
SG: { factor: 0.408, unit: "kg CO2e per kWh", source: "IEA_2026", year: 2026, name: "Singapore" },
AU: { factor: 0.510, unit: "kg CO2e per kWh", source: "IEA_2026", year: 2026, name: "Australia" },
NZ: { factor: 0.098, unit: "kg CO2e per kWh", source: "IEA_2026", year: 2026, name: "New Zealand", note: "Geothermal + hydro" },
// US — EPA eGRID 2024 subregions
// National average is almost never the right choice for a specific site.
US: { factor: 0.386, source: "EPA_2024", name: "United States (national avg)" },
US_NYUP: { factor: 0.1249, source: "EPA_2024", name: "NYUP — Upstate New York", note: "Hydro 31% + nuclear 31%" },
US_WECC_CAMX: { factor: 0.2265, source: "EPA_2024", name: "CAMX — California", note: "Gas 46% + solar 20%" },
US_ERCT: { factor: 0.3512, source: "EPA_2024", name: "ERCT — Texas (ERCOT)", note: "Gas 47% + wind 23%" },
US_RFCW: { factor: 0.4563, source: "EPA_2024", name: "RFCW — Ohio Valley", note: "Coal 31% + gas 32%" },
US_SRMW: { factor: 0.6260, source: "EPA_2024", name: "SRMW — SERC Midwest", note: "Coal 59% — highest subregion" },
};
Every entry carries source and year. These render in the calculator UI next to the result — not hidden in a tooltip, not buried in a footnote. If someone is using this number in a regulatory disclosure, they need to see the citation inline.
The lookup function
/**
* Look up a grid emission factor.
*
* @param {string} countryCode ISO 3166-1 alpha-2 or US eGRID subregion key
* @param {object} brain window.gcMasterBrain (injected server-side)
* @returns {{ factor: number, source: string, year: number, name: string } | null}
*/
function getGridFactor(countryCode, brain) {
const key = countryCode.toUpperCase();
const entry = brain?.grid?.[key] ?? null;
if (!entry) {
console.error(`[GC] Grid factor not found for key: "${key}"`);
return null;
}
return entry;
}
Tiny, deterministic, no fetch. The ?? guard means a missing or malformed Master Brain injection fails gracefully — the calculator surfaces an error state rather than silently outputting NaN * 0 = 0 into a user's report.
The calculation
/**
* Scope 2 location-based electricity emissions.
*
* @param {number} kWh Electricity consumed (kWh)
* @param {string} countryCode Grid lookup key
* @param {object} brain window.gcMasterBrain
* @returns {{ kgCO2e: number, tCO2e: number, citation: string } | null}
*/
function calcScope2LocationBased(kWh, countryCode, brain) {
const grid = getGridFactor(countryCode, brain);
if (!grid) return null;
const kgCO2e = kWh * grid.factor;
return {
kgCO2e: +kgCO2e.toFixed(4),
tCO2e: +(kgCO2e / 1000).toFixed(6),
citation: `${grid.source} (${grid.year}) — ${grid.name}`,
factor: grid.factor,
note: grid.note ?? null,
};
}
The return object always includes citation. Downstream rendering code has no excuse to display a number without its source.
Rendering the citation inline
function renderScope2Result(result, outputEl, citationEl) {
if (!result) {
outputEl.textContent = "—";
citationEl.textContent = "Factor not available for this region.";
return;
}
outputEl.textContent = `${result.tCO2e} tCO₂e`;
citationEl.textContent = result.citation;
if (result.note) {
citationEl.textContent += ` · ${result.note}`;
}
}
The output element should have font-family: 'JetBrains Mono', monospace; font-variant-numeric: tabular-nums in CSS. When a user changes the kWh input, digits update in-place without layout shift — a subtle but important signal that this is a precision instrument, not a toy.
The stale factor problem
Grid factors change every year. IEA publishes in April. DEFRA publishes every June. EPA eGRID publishes in January.
The UK grid factor dropped 15% in the DEFRA 2025 release — from 0.207 to 0.177 kg CO₂e/kWh. Any calculator that hardcoded the 2024 value is now overstating UK Scope 2 by 17%. For a 10 GWh/year operation, that's roughly 300 tCO₂e of phantom emissions in a filed disclosure.
The pattern I use to prevent this:
- All factors live in one file with a version constant (
GC_MB_VERSION = '2025.6') - The version renders in every calculator's footer:
Data: DEFRA 2025 / IEA 2026 / EPA eGRID 2024 - A fallback version constant is gated in CI — it must match the Master Brain version on every deploy
- The update calendar is hardcoded as a comment in the source file so it cannot be missed
If a factor update deploys with a version bump but the fallback table is not updated in the same commit, a console.error fires on every page load identifying the stale page by post ID. One-click diagnosis.
What this gives you
A lookup that is:
- Deterministic — same inputs, same output, every time, no network round-trip
- Auditable — every result carries its source citation inline
- Maintainable — one file to update annually, propagates to every calculator automatically
- GHG Protocol compliant — location-based correctly separated from market-based; Scope 3 Category 3 (fuel and energy-related activities) handled as a distinct calculation
The full dataset — 50 countries and US eGRID subregions — is live at greencalculus.com.
Sources: IEA Global Energy Review 2026 · DEFRA GHG Conversion Factors 2025 (DESNZ) · EPA eGRID Summary Tables 2022, published January 2024 · GHG Protocol Scope 2 Guidance
Top comments (0)