DEV Community

Jeremiah Say
Jeremiah Say

Posted on

CSRD/ESRS E1 disclosure requirements, translated into data fields — a developer's map

ESRS E1 is the climate standard inside the EU's Corporate Sustainability Reporting Directive. It tells companies what to disclose. It does not tell developers what data to collect, what units to store, or how to structure the fields that feed those disclosures.

That translation gap is where most carbon software goes wrong. The sustainability team thinks they're collecting what the standard requires. The developer builds fields that seem right. Nobody maps one to the other explicitly. By the time the first CSRD report needs to be filed, the data model is missing half of what E1 demands and contains a lot that it doesn't need.

This is the map I built when designing the data layer at GreenCalculus.com. It covers every material E1 datapoint and the field structure behind each one.


What ESRS E1 actually requires

ESRS E1 is organised into disclosure requirements (DRs). For GHG emissions, the core ones are:

  • E1-6: Gross Scope 1, 2 and 3 GHG emissions
  • E1-7: GHG removals and carbon credits
  • E1-5: Energy consumption and mix
  • E1-4: Targets related to climate change mitigation
  • E1-3: Actions and resources for climate transition
  • E1-1: Transition plan for climate change mitigation (Phase 1 companies)

Each DR maps to specific datapoints. Each datapoint maps to specific fields. Here is that mapping, section by section.


E1-6: GHG emissions — the core inventory

This is the largest DR. Everything here feeds a GHG inventory that must comply with GHG Protocol Corporate Standard or an equivalent methodology.

Scope 1 — direct emissions

{
  scope1: {
    gross_tco2e: number,         // Total Scope 1, tCO2e, GWP basis declared separately
    gwp_basis: "AR5" | "AR6",    // Must be disclosed. AR5 = IPCC Fifth Assessment Report
    includes_biogenic: false,    // Biogenic CO2 reported out of scope, never in gross total
    biogenic_co2_tco2e: number,  // Reported separately, labelled as out-of-scope
    breakdown: {
      stationary_combustion: number,   // Boilers, generators, furnaces
      mobile_combustion: number,       // Company-owned fleet
      process_emissions: number,       // Chemical/industrial reactions (e.g. BF-BOF steel)
      fugitive_emissions: number,      // Refrigerant leaks, methane from coal/oil/gas
    },
    methodology: string,         // "GHG Protocol Corporate Standard" or equivalent
  }
}
Enter fullscreen mode Exit fullscreen mode

Two fields here that developers routinely miss:

biogenic_co2_tco2e — Biogenic CO₂ from biomass combustion (wood pellets, biogas, etc.) is explicitly excluded from the gross Scope 1 total under GHG Protocol and ESRS E1. It must be reported as a separate line item labelled "out of scope." If your data model rolls it into gross_tco2e, the disclosure is wrong. Every bioenergy fuel entry in our Master Brain carries a separate biogenic_co2 key for exactly this reason.

process_emissions — Steel companies using blast furnace–basic oxygen furnace (BF-BOF) steelmaking classify coking coal use as a process emission, not stationary combustion. ESRS E1-6 requires this disaggregation. A flat scope1_total field without the breakdown loses this.

Scope 2 — purchased electricity, steam, heat, cooling

{
  scope2: {
    location_based_tco2e: number,   // Average grid intensity × consumption
    market_based_tco2e: number,     // Supplier-specific / EAC / REGO factor
    gwp_basis: "AR5" | "AR6",
    grid_factor_source: string,     // "IEA_2026" | "DEFRA_2025" | "EPA_2024"
    grid_factor_country: string,    // ISO 3166-1 alpha-2, e.g. "DE"
    eac_coverage_percent: number,   // % of consumption covered by energy attribute certificates
    residual_mix_factor: number,    // kg CO2e/kWh — used for uncovered market-based portion
  }
}
Enter fullscreen mode Exit fullscreen mode

GHG Protocol Scope 2 Guidance requires both location-based and market-based figures wherever data is available. ESRS E1-6 inherits this. A single scope2_total field is not enough — you need both methods stored separately so the report can disclose both.

eac_coverage_percent matters for the market-based figure. If a company has REGOs covering 40% of its UK electricity, the market-based calculation applies the REGO factor to 40% and the residual mix factor to the remaining 60%. If your model only stores a single factor, this is impossible to reconstruct for audit.

Scope 3 — value chain emissions

ESRS E1-6 requires Scope 3 disclosure across all 15 GHG Protocol categories. Twelve of those 15 are mandatory under CSRD (the three optional ones are upstream/downstream leased assets and franchises, which are required only where material):

{
  scope3: {
    total_tco2e: number,
    gwp_basis: "AR5" | "AR6",
    categories: {
      cat1_purchased_goods_services:          { tco2e: number, method: "spend" | "activity" | "hybrid", required_csrd: true },
      cat2_capital_goods:                     { tco2e: number, method: string, required_csrd: true },
      cat3_fuel_energy_activities:            { tco2e: number, method: string, required_csrd: true },
      cat4_upstream_transport:                { tco2e: number, method: string, required_csrd: true },
      cat5_waste_operations:                  { tco2e: number, method: string, required_csrd: true },
      cat6_business_travel:                   { tco2e: number, method: string, required_csrd: true },
      cat7_employee_commuting:                { tco2e: number, method: string, required_csrd: true },
      cat8_upstream_leased_assets:            { tco2e: number, method: string, required_csrd: false },
      cat9_downstream_transport:              { tco2e: number, method: string, required_csrd: false },
      cat10_processing_sold_products:         { tco2e: number, method: string, required_csrd: false },
      cat11_use_of_sold_products:             { tco2e: number, method: string, required_csrd: true },
      cat12_end_of_life_sold_products:        { tco2e: number, method: string, required_csrd: true },
      cat13_downstream_leased_assets:         { tco2e: number, method: string, required_csrd: false },
      cat14_franchises:                       { tco2e: number, method: string, required_csrd: false },
      cat15_investments:                      { tco2e: number, method: string, required_csrd: false },
    },
    omitted_categories: [
      {
        category: "cat8_upstream_leased_assets",
        reason: "Not material — no upstream leased assets in reporting boundary",
      }
    ],
  }
}
Enter fullscreen mode Exit fullscreen mode

The omitted_categories array is not optional housekeeping — ESRS E1-6 requires companies to explicitly justify any omitted category. If you omit Cat 8 without a documented reason, the auditor will flag it. Build the justification field into the model from day one.

The method field on each category matters for methodology transparency. Spend-based (DEFRA economic factors) and activity-based (physical quantities × emission factors) produce materially different figures for the same category and must be disclosed.


E1-7: GHG removals and carbon credits

This DR is where most platforms are completely empty. ESRS E1-7 requires separate disclosure of:

{
  removals: {
    total_tco2e_removed: number,    // Must not be netted against gross emissions in E1-6
    breakdown: {
      forestry_afforestation: number,
      forestry_reforestation: number,
      soil_sequestration: number,
      blue_carbon: number,
      direct_air_capture: number,
      bioenergy_ccs: number,
    },
    permanence_risk_note: string,   // Required for biological removal pathways
  },
  carbon_credits: {
    total_credits_used: number,     // tCO2e
    standard: string,               // "Gold Standard" | "VCS" | "Article 6.4" | etc.
    vintage_year: number,
    project_ids: string[],
    used_for: "net_zero_claim" | "carbon_neutral_claim" | "offsetting_only",
    credits_excluded_from_scope_targets: boolean,  // SBTi: credits cannot count toward scope targets
  }
}
Enter fullscreen mode Exit fullscreen mode

The critical rule: removals must never be netted against gross emissions in E1-6. The gross figures in E1-6 are gross. Removals are disclosed separately in E1-7. If your data model subtracts removals to produce a "net" figure and feeds that into the E1-6 fields, the disclosure is non-compliant.

Carbon credits are similarly ring-fenced. Under SBTi and increasingly under ESRS E1, offsets cannot be counted against scope-based emission reduction targets. The credits_excluded_from_scope_targets boolean forces this distinction to be explicit in the data model rather than handled ad hoc at report generation time.


E1-5: Energy consumption and mix

{
  energy: {
    total_consumption_mwh: number,
    breakdown: {
      fossil_fuel_mwh: number,
      nuclear_mwh: number,
      renewable_self_generated_mwh: number,
      renewable_purchased_mwh: number,
      renewable_eac_covered_mwh: number,
    },
    renewable_share_percent: number,   // Calculated field: renewable / total × 100
    energy_intensity: {
      value: number,                   // tCO2e per unit of activity
      denominator_unit: string,        // "MWh" | "EUR_revenue" | "tonne_product" | etc.
      denominator_value: number,
    },
  }
}
Enter fullscreen mode Exit fullscreen mode

energy_intensity is required by E1-5 and is the metric most companies get wrong at the data model level. The denominator must be declared — intensity against revenue means something different from intensity against tonnes of product. Both are valid but they are not interchangeable, and the standard requires disclosure of which one you used.


E1-4: Climate targets

{
  targets: [
    {
      id: string,                       // Internal reference
      scope_coverage: ("scope1" | "scope2" | "scope3")[],
      target_type: "absolute" | "intensity",
      baseline_year: number,
      baseline_tco2e: number,
      target_year: number,
      target_reduction_percent: number,
      aligned_framework: "SBTi" | "Paris_1.5C" | "Paris_2C" | "net_zero" | "custom",
      validated_by: string | null,      // "SBTi" if externally validated
      interim_milestones: [
        { year: number, reduction_percent: number }
      ],
      offsets_included_in_target: false,  // Must be false for SBTi-aligned targets
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

The offsets_included_in_target field is the one that will trip up companies using offsets to meet science-based targets. SBTi is explicit: offsets cannot be used to meet Scope 1, 2, or 3 reduction targets. If this field is missing from your model, there is no machine-readable way to verify compliance at report time.


The GWP basis problem

Every GHG emission figure in ESRS E1 must be accompanied by a declared GWP basis. AR5 and AR6 produce different CO₂e values for the same gas — CH₄ is 28 (AR5) vs 29.8 (AR6) for biogenic methane, and 30 vs 27.9 for fossil methane.

The correct field structure is not a single tco2e number. It is:

{
  gross_tco2e_ar5: number,   // For regulatory reporting (most frameworks still use AR5)
  gross_tco2e_ar6: number,   // For comparability with AR6-based targets
  gwp_basis_primary: "AR5",  // Which basis the disclosed figure uses
}
Enter fullscreen mode Exit fullscreen mode

Storing both values costs two fields. Not storing both means a methodology change from AR5 to AR6 (which is coming as ESRS matures) requires recalculating every historical figure from scratch rather than reading the already-stored alternate value.


The field map as a WordPress custom post meta schema

If you're building this in WordPress, the CSRD inventory post type meta fields map cleanly to the structure above. Here's a minimal registration:

function gc_register_csrd_inventory_meta(): void {

    $fields = [
        // E1-6 Scope 1
        '_csrd_s1_gross_tco2e'              => 'number',
        '_csrd_s1_biogenic_co2_tco2e'       => 'number',
        '_csrd_s1_stationary_tco2e'         => 'number',
        '_csrd_s1_mobile_tco2e'             => 'number',
        '_csrd_s1_process_tco2e'            => 'number',
        '_csrd_s1_fugitive_tco2e'           => 'number',

        // E1-6 Scope 2
        '_csrd_s2_location_tco2e'           => 'number',
        '_csrd_s2_market_tco2e'             => 'number',
        '_csrd_s2_eac_coverage_pct'         => 'number',
        '_csrd_s2_grid_factor_source'       => 'string',
        '_csrd_s2_grid_country'             => 'string',

        // E1-6 Scope 3 (JSON-encoded category array)
        '_csrd_s3_categories_json'          => 'string',
        '_csrd_s3_omitted_json'             => 'string',

        // E1-5 Energy
        '_csrd_energy_total_mwh'            => 'number',
        '_csrd_energy_renewable_pct'        => 'number',
        '_csrd_energy_intensity_value'      => 'number',
        '_csrd_energy_intensity_denominator'=> 'string',

        // E1-7 Removals
        '_csrd_removals_tco2e'              => 'number',
        '_csrd_credits_tco2e'               => 'number',
        '_csrd_credits_standard'            => 'string',

        // GWP basis (applies to all figures above)
        '_csrd_gwp_basis'                   => 'string',
        '_csrd_reporting_year'              => 'integer',
        '_csrd_baseline_year'               => 'integer',
    ];

    foreach ( $fields as $key => $type ) {
        register_post_meta( 'gc_csrd_inventory', $key, [
            'show_in_rest'      => true,
            'single'            => true,
            'type'              => $type,
            'auth_callback'     => fn() => current_user_can( 'edit_posts' ),
            'sanitize_callback' => $type === 'number' ? 'floatval' : 'sanitize_text_field',
        ] );
    }
}
add_action( 'init', 'gc_register_csrd_inventory_meta' );
Enter fullscreen mode Exit fullscreen mode

The JSON-encoded fields (_csrd_s3_categories_json, _csrd_s3_omitted_json) store the full category breakdown as a serialised array rather than as 30 individual meta keys. This keeps the meta table manageable and makes it easy to pass the full Scope 3 breakdown to a REST endpoint or report generator in a single get_post_meta() call.


What this model prevents

Four failure modes that show up in CSRD first filings when the data model wasn't designed for the standard:

1. Biogenic netting — biomass CO₂ rolled into gross Scope 1 total. Non-compliant from the first figure.

2. Single Scope 2 figure — no location/market split stored. Cannot produce a compliant E1-6 disclosure. Requires a full data backfill.

3. Missing Scope 3 omission justifications — categories silently absent from the report. Auditor asks why Cat 8 is missing. No documented reason exists.

4. Net figures in gross fields — removals or credits subtracted before the gross total is stored. The E1-6 gross figure is wrong and there is no way to reconstruct the correct number without raw inputs.

All four are architectural decisions, not data entry errors. The only way to prevent them is to build the field structure before the first data collection cycle, not after the first disclosure.


The GreenCalculus data layer implements these fields across 1,000+ calculators and tools. Full methodology documentation at greencalculus.com.

Top comments (0)