
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.
- 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.
- 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 thenordpool
directory from yourconfig/custom_components
folder yourself. - 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.
- 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:
- Go toΒ Developer Tools > Actions.
- In the “Service” dropdown, select
Nord Pool: Get prices
. - Choose your newly created config entry from the UI.
- Switch to YAML mode.
- 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!
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?
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.
im using tibber to get prices so im cool, just need to get the AIO hours π
Just one question, how will it calculate for example 8 cheapest hours when 15min hits?
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.
You state “Go to Developer Tools > Services.” > in my config i need to go to “Go to Developer Tools > Actions.”?
You’re absolutely correct, a mistake in my part.
Thanks for noticing, it’s corrected on the post now as well π