Home Assistant: Migrating to the Official Nord Pool Integration

I’ve been a happy user of the Nord Pool custom component for a long time, and it has served me well! However, with Home Assistant recently introducing its own official Nord Pool integration, and a significant market change on the horizon, it’s time to make the switch.

On October 1st, 2025, the Nord Pool market is transitioning to a 15-minute Market Time Unit (MTU). While the future of the custom component is uncertain after this change, the official Home Assistant integration has confirmed it will support the 15-minute MTU right out of the box. There might be a few hiccups during the transition, but having official support is a major advantage.

In this post, I’ll walk you through the exact steps I took to migrate my system from the custom component to the official integration, ensuring my smart home stays ahead of the curve.

Note: This guide uses 60min MTU with Nord Pool core integration and most probably will need updates once 15min mtu has gone live.

Key Technical Differences

The biggest change between the two integrations is how they handle data. The official Nord Pool integration is built around services, while the custom component exposed everything as attributes on a sensor. This means that to get price data with the new integration, we need to actively call a service.

Another major difference is that the official integration provides only the raw price data from Nord Pool (e.g., in €/MWh for Finland) without taxes. The custom component, on the other hand, allowed you to include VAT and other additional costs directly in the sensor’s output. We’ll need to replicate this functionality using template sensors.

Step 1: Away With the Old, In With the New

Because the custom and official components share the same name (nordpool), we can’t have both installed simultaneously. This means the migration needs to be done in one goβ€”a “big bang” approach.

Let’s get straight to it.

  1. Uninstall the Custom Component: Navigate to Settings > Devices & Services in Home Assistant, find your existing Nord Pool integration, and click the three dots to uninstall it.
  2. Remove from HACS (if applicable): If you installed the custom component via HACS, you must also uninstall it from the HACS interface. This will properly remove the files from your custom_components folder. If you installed it manually, you’ll need to delete the nordpool directory from your config/custom_components folder yourself.
  3. Reboot Home Assistant: A full reboot is essential to ensure the old component is completely unloaded.

Once your Home Assistant has rebooted, it’s time to install the official integration.

  1. Install the Official Integration: Go to Settings > Devices & Services, click + Add Integration, search for “Nord Pool,” and follow the prompts to configure it with your preferred settings.

Step 2: Find Your Configuration ID

With the new service-based approach, you’ll need a config_entry ID to tell Home Assistant which Nord Pool instance you want to get data from. Think of it as a replacement for the entity_id you might have used before.

The easiest way to find this ID is:

  1. Go toΒ Developer Tools > Actions.
  2. In the “Service” dropdown, select Nord Pool: Get prices.
  3. Choose your newly created config entry from the UI.
  4. Switch to YAML mode.
  5. Voila! The config_entry ID will be displayed. Copy this value for the next steps.

Step 3: Rebuilding the ApexCharts Price Graph

Since ApexCharts can’t call services directly to populate a graph, we need to create an intermediary: a template sensor that calls the Nord Pool service and stores the prices.

The template sensor below does exactly that. It calls the service periodically to fetch today’s and tomorrow’s prices. Don’t worry about spamming the API; the integration caches the data locally, so frequent updates won’t cause excessive network traffic.

This template also includes attributes for tax and additional_cost, making it easy to replicate the final price calculation you had with the custom component.

template:
  - triggers:
      - trigger: time_pattern
        minutes: /1
      - trigger: homeassistant
        event: start
    actions:
      - action: nordpool.get_prices_for_date
        data:
          config_entry: 01K4QBH1H606SDS1V28KGR32K7
          date: "{{ now().date() }}"
          areas: FI
          currency: EUR
        response_variable: today_price
      - action: nordpool.get_prices_for_date
        data:
          config_entry: 01K4QBH1H606SDS1V28KGR32K7
          date: "{{ now().date() + timedelta(days=1) }}"
          areas: FI
          currency: EUR
        response_variable: tomorrow_price
    sensor:
      - name: Electricity prices
        unique_id: electricity_prices
        unit_of_measurement: "snt/kWh"
        icon: mdi:cash
        state: >
          {%- set region = (this.attributes.get('region', 'FI') | string) -%}
          {%- set tax = (this.attributes.get('tax', 1.0) | float) -%}
          {%- set additional_cost = (this.attributes.get('additional_cost', 0.0) | float) -%}

          {% if (today_price is mapping) and (tomorrow_price is mapping) %}
            {% set data = namespace(prices=[]) %}
            {% for state in today_price[region] %}
              {% set data.prices = data.prices + [(((state.price/10 | float)  * tax + additional_cost)) | round(3, default=0)] %}
            {% endfor %}
            {% for state in tomorrow_price[region] %}
              {% set data.prices = data.prices + [(((state.price/10 | float) * tax + additional_cost)) | round(3, default=0)] %}
            {% endfor %}
            {{min(data.prices)}}
          {% else %}
            unavailable
          {% endif %}
        attributes:
          tomorrow_valid: >
            {%- set region = (this.attributes.get('region', 'FI') | string) -%}
            {%- if (tomorrow_price is mapping) %}
              {%- if tomorrow_price[region] | list | count > 0 -%}
                {{ true | bool }}
              {%- else %}
                {{ false | bool }}
              {%- endif %}
            {%- else %}
              {{ false | bool }}
            {%- endif %}
          data: >
            {%- set region = (this.attributes.get('region', 'FI') | string) -%}
            {%- set tax = (this.attributes.get('tax', 1.0) | float) -%}
            {%- set additional_cost = (this.attributes.get('additional_cost', 0.0) | float) -%}

            {% if (today_price is mapping) and (tomorrow_price is mapping) %}
            {% set data = namespace(prices=[]) %}
              {% for state in today_price[region] %}
                {% set local_start = as_datetime(state.start).astimezone().strftime('%Y-%m-%d %H:%M:%S') %}
                {% set local_end = as_datetime(state.end).astimezone().strftime('%Y-%m-%d %H:%M:%S') %}
                {% set data.prices = data.prices + [{'start':local_start, 'end':local_end, 'price': (((state.price/10 | float) * tax + additional_cost)) | round(3, default=0)}] %}
              {% endfor %}
              {% for state in tomorrow_price[region] %}
                {% set local_start = as_datetime(state.start).astimezone().strftime('%Y-%m-%d %H:%M:%S') %}
                {% set local_end = as_datetime(state.end).astimezone().strftime('%Y-%m-%d %H:%M:%S') %}
                {% set data.prices = data.prices + [{'start':local_start, 'end':local_end, 'price': (((state.price/10 | float) * tax + additional_cost)) | round(3, default=0)}] %}
              {% endfor %}
              {{data.prices}}
            {% else %}
              []
            {% endif %}
          tax: 1.255
          additional_cost: 0
          region: FI

After creating this sensor and reloading your template entities (Developer Tools > YAML > Template Entities), you’ll have sensor.electricity_prices ready to use. Here is the ApexCharts card configuration, which is very similar to the old setup:

type: custom:apexcharts-card
experimental:
  color_threshold: true
header:
  show: true
  title: NordPool electricity prices (today/tomorrow)
  show_states: true
  colorize_states: true
graph_span: 48h
span:
  end: day
  offset: +1d
now:
  show: true
  label: Now
series:
  - entity: sensor.electricity_prices
    type: column
    show:
      extremas: true
      in_header: before_now
    float_precision: 3
    data_generator: |
      return entity.attributes.data.map((start, index) => {
        return [new Date(start["start"]).getTime(), entity.attributes.data[index]["price"]];
      });
    color_threshold:
      - value: 0
        color: green
      - value: 10
        color: orange
      - value: 30
        color: red

Step 4: Updating the Energy Dashboard

The official integration creates a sensor for the current price automatically (e.g., sensor.nord_pool_fi_current_price), which is perfect for the Energy Dashboard. However, this sensor shows the raw price. If you want your Energy Dashboard costs to reflect VAT, you’ll need another simple template sensor.

This one adds the 25.5% VAT to the raw price.

template:
  - sensor:
      name: Nord Pool current price with taxes
      unique_id: nordpool_current_price_with_taxes
      unit_of_measurement: "snt/kWh"
      state: >
        {{ states('sensor.nord_pool_fi_current_price') | float * 1.255 | round }}

You can then use this new sensor.nordpool_current_price_with_taxes in your Energy Dashboard settings for real-time costs.

Step 5: Configuring AIO Energy Management

I use the AIO Energy Management integration to calculate the cheapest hours of the day for running high-consumption devices. Version 0.5.0 introduced support for the 15-minute MTU and, crucially, price templating. This is necessary because the raw price from the Nord Pool service doesn’t include taxes or fees.

In my configuration below, I use the price_modifications template to transform the raw price from €/MWh into cents/kWh (snt/kWh) and add the 25.5% VAT.

Here is my updated configuration for AIO Energy Management for all my devices (water heater, outdoor spa and garage heating):

aio_energy_management:
    calendar:
        unique_id: energy_management_calendar
        name: Energy Management Calendar
    cheapest_hours:
      - nordpool_official_config_entry: 01K4QBH1H606SDS1V28KGR32K7
        unique_id: cheapest_hours_water_boiler
        name: Cheapest Hours Water Boiler
        first_hour: 21
        last_hour: 12
        starting_today: true
        number_of_hours: 6
        sequential: false
        failsafe_starting_hour: 1
        price_modifications: >
          {%- set as_snt = price / 10.0 %}
          {%- set with_taxes = (as_snt * 1.255) | float %}
          {{ with_taxes }}
      - nordpool_official_config_entry: 01K4QBH1H606SDS1V28KGR32K7
        unique_id: cheapest_hours_hot_tub
        name: Cheapest Hours Hot Tub
        first_hour: 0
        last_hour: 23
        starting_today: false
        number_of_hours: 5
        sequential: false
        max_price: 5.0
        price_modifications: >
          {%- set as_snt = price / 10.0 %}
          {%- set with_taxes = (as_snt * 1.255) | float %}
          {{ with_taxes }}
      - nordpool_official_config_entry: 01K4QBH1H606SDS1V28KGR32K7
        unique_id: cheapest_hours_garage_heating
        name: Cheapest Hours Garage Heating
        first_hour: 20
        last_hour: 14
        starting_today: true
        number_of_hours: sensor.garage_heating_number_of_hour
        sequential: false
        trigger_hour: 20
        failsafe_starting_hour: 1
        price_modifications: >
          {%- set as_snt = price / 10.0 %}
          {%- set with_taxes = (as_snt * 1.255) | float %}
          {{ with_taxes }}

Final Steps and Automations

I also had many automations that relied on the old custom component, and as expected, they all broke during this update. The simplest ones just needed an entity ID change, but othersβ€”like energy price push notifications or automations that change my EV charger’s color based on priceβ€”required more significant changes to work with the new service-based approach.

I won’t be diving into specific automation examples in this post, but if that’s something you’d like to see, let me know in the comments, and I might write a follow-up article!

Did you find this guide helpful? You can keep the blog going by bidding me a coffee!

7 Replies to “Home Assistant: Migrating to the Official Nord Pool Integration”

  1. so after i changed to official nordpool ill just need to change to
    – nordpool_official_config_entry: XXXXXXXXXXXXXXXXXXXXX
    in AIO to get the prices?

    and if i want to use apex i need to make the template?

    1. Yes, except uninstalling custom component version and installing + configuring Home Assistant core version obviously.

      Aio works just by changing the config entry, no other changes necessary unless you want to include tariffs or similar for calculations.

      Apex graph currently needs a bit more as there’s no way to get the data from service directly.

      1. im using tibber to get prices so im cool, just need to get the AIO hours πŸ˜‰

  2. Just one question, how will it calculate for example 8 cheapest hours when 15min hits?

    1. number_of_hours is number of hours, no changes in there.
      Example:
      You want to get eight hours out so you set number_of_hours = 8
      When using mtu 15, you get 8*15 slots so total 8 hours πŸ™‚

      I’m planning to implement number_of_slots at some point that follows selected mtu allowing user to select e.g. 1.5hours, but that’s not in progress yes.

      And one more point, by default, the implementation uses 60min mtu. So even if data comes with 15min mtu and no mtu value is set or set to 60, it will combine the full hours by mean value.
      Changing mtu to 15 will use 15min slot sizes, only available after 1st October of course.

  3. You state “Go to Developer Tools > Services.” > in my config i need to go to “Go to Developer Tools > Actions.”?

    1. You’re absolutely correct, a mistake in my part.

      Thanks for noticing, it’s corrected on the post now as well πŸ™‚

Leave a Reply

Your email address will not be published. Required fields are marked *