Skip to main content

Changed config entry state transitions

· 2 min read

Config entry state transitions when unloading and removing entries has been modified:

  • A new state ConfigEntryState.UNLOAD_IN_PROGRESS is added, which is set before calling the integration's async_unload_entry
    Rationale:

    • Make it easier to write cleanup code which should run after the last config entry has been unloaded
    • Improve debugging of issues related to reload and unload of config entries
  • The config entry state is set to ConfigEntryState.FAILED_UNLOAD when the integration's async_unload_entry returns False
    Rationale:

    • If async_unload_entry returns False, we can't assume the integration is still loaded, most likely it has partially unloaded itself, especially considering this is the pattern we recommend:
    async def async_unload_entry(hass: HomeAssistant, entry: MyConfigEntry) -> bool:
    """Unload a config entry."""
    # async_unload_platforms returns False if at least one platform did not unload
    if (unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS)):
    entry.runtime_data.listener()
    # Finish cleanup not related to platforms
    return unload_ok
  • The config entry is removed from hass.config_entries before calling the integration's async_remove_entry is called
    Rationale:

    • Make it easier to write cleanup code which should run after the last config entry has been removed

Custom integration authors need to review and update their integrations' async_unload_entry and async_remove_entry if needed. The most common pattern which requires an update is this:

async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
loaded_entries = [
entry
for entry in hass.config_entries.async_entries(DOMAIN)
if entry.state is ConfigEntryState.LOADED
]
if len(loaded_entries) == 1:
# The last config entry is being unloaded, release shared resources, unregister services etc.
...

This can now be simplified, if the custom integration's minimum Home Assistant version is set to 2025.3.0:

async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
if not hass.config_entries.async_loaded_entries(DOMAIN):
# The last config entry is being unloaded, release shared resources, unregister services etc.
...

If the custom integration needs to be backwards compatible with previous releases of Home Assistant Core:

async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
other_loaded_entries = [
_entry
for _entry in hass.config_entries.async_loaded_entries(DOMAIN)
if _entry.entry_id != entry.entry_id
]
if not other_loaded_entries:
# The last config entry is being unloaded, release shared resources, unregister services etc.
...

Check the config entry documentation, and the home assistant core PR #138522 for additional background.

Backup agents

· One min read

In the January release of 2025, we introduced a new feature for backups called backup agents.

Previously, backup platforms were used only to pause or prepare integration operations before creating a backup and to execute a post-backup operation. With the introduction of backup agents, the backup platform now allows integrations to add one or more backup agents that can upload backups to some local or remote location.

The first integration to implement a backup agent was the Home Assistant Cloud integration. In the February release of 2025, three more integrations implemented backup agents: Google Drive, OneDrive and Synology DSM. If you are an integration author, and want to add support for your favorite backup location, you can find more information on how to implement backup agents in the developer documentation.

Support for config subentries

· One min read

Config entries now have a new data type called “config subentry”. Config subentries are owned by a config entry and set up as part of async_setup_entry. Config subentries are created by config subentry flows and updated by config subentry reconfigure flows. This works similarly to how we create/edit config entries.

This makes it easier for integration authors to allow users to add, modify and remove pieces of configuration which share some common resource, for example a cloud account or an MQTT broker.

The architecture discussion gives more background.

Energy by distance units

· One min read

Summary of changes

A new ENERGY_DISTANCE device class for representing energy per distance is now available for number and sensor entities, together with automatic unit conversion based on the unit system. This new device class can be used, for example, on entities that show the amount of electric energy consumed by an electric car. A corresponding UnitOfEnergyDistance unit enumerator, and EnergyDistanceConverter converter class have been added to support the new device class.

The following units are available:

  • kWh/100km
  • mi/kWh
  • km/kWh

More details can be found in the Number documentation and Sensor documentation

Inverse Units

Implementing EnergyDistanceConverter has also resulted in support for inverse units in BaseUnitConverter. This simplifies adding new units that are inverses of one another, e.g., kWh/100km and km/kWh.

Relocate dhcp/ssdp/usb/zeroconf ServiceInfo models

· One min read

Summary of changes

To reduce current reliance on optional integrations for names that are in essence used as helpers, the following ServiceInfo models have been relocated:

  • DhcpServiceInfo from homeassistant.components.dhcp to homeassistant.helpers.service_info.dhcp
  • SsdpServiceInfo from homeassistant.components.ssdp to homeassistant.helpers.service_info.ssdp
  • UsbServiceInfo from homeassistant.components.usb to homeassistant.helpers.service_info.usb
  • ZeroconfServiceInfo from homeassistant.components.zeroconf to homeassistant.helpers.service_info.zeroconf

To update your integration:

  1. Replace the import statements as shown in the examples below
  2. Test your integration with the new imports

The old import locations are deprecated and will be removed in Home Assistant 2026.2.

Examples

# Old
# from homeassistant.components.dhcp import DhcpServiceInfo
# from homeassistant.components.ssdp import SsdpServiceInfo
# from homeassistant.components.usb import UsbServiceInfo
# from homeassistant.components.zeroconf import ZeroconfServiceInfo

# New
from homeassistant.helpers.service_info.dhcp import DhcpServiceInfo
from homeassistant.helpers.service_info.ssdp import SsdpServiceInfo
from homeassistant.helpers.service_info.usb import UsbServiceInfo
from homeassistant.helpers.service_info.zeroconf import ZeroconfServiceInfo

class MyConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle a config flow."""

async def async_step_dhcp(self, discovery_info: DhcpServiceInfo) -> ConfigFlowResult:
"""Handle dhcp discovery."""
...

async def async_step_ssdp(self, discovery_info: SsdpServiceInfo) -> ConfigFlowResult:
"""Handle ssdp discovery."""
...

async def async_step_usb(self, discovery_info: UsbServiceInfo) -> ConfigFlowResult:
"""Handle usb discovery."""
...

async def async_step_zeroconf(self, discovery_info: ZeroconfServiceInfo) -> ConfigFlowResult:
"""Handle zeroconf discovery."""
...

New area device class

· One min read

Summary of changes

A new AREA device class is now available for number and sensor entities, together with automatic unit conversion based on the unit system. A corresponding UnitOfArea unit enumerator, and AreaConverter converter class have been added to support the new device class.

Backward compatibility

The AREA_SQUARE_METERS constant has been deprecated and will be removed in Home Assistant 2025.12. Custom integrations should be adjusted to use UnitOfArea.SQUARE_METERS.

More details can be found in the Number documentation and Sensor documentation

Moving to Pydantic v2

· One min read

Pydantic is a widely used library in Python for data validation. On June 30, 2023, Pydantic v2 was released, introducing significant changes that are not backward compatible with Pydantic v1.

Starting with Home Assistant Core 2025.1, Pydantic v2 will replace v1. If your custom integration uses Pydantic, it must be updated to support Pydantic v2 to keep working in the upcoming release.

Over the past year, our community has worked hard to ensure that the libraries used by Home Assistant Core are compatible with both Pydantic v1 and v2. This dual compatibility has helped make our transition to Pydantic v2 as smooth as possible.

For a quick migration, you can use the Pydantic v1 shims included in Pydantic v2. Detailed information about using these shims in a v1/v2 environment can be found in the Pydantic migration guide.

Use Kelvin as the preferred color temperature unit

· 2 min read

Summary of changes

In October 2022, Home Assistant migrated the preferred color temperature unit from mired to kelvin.

It is now time to add deprecation warnings for the corresponding attributes, constants and properties:

  • Deprecate state and capability attributes ATTR_COLOR_TEMP, ATTR_MIN_MIREDS and ATTR_MAX_MIREDS
  • Deprecate constants ATTR_KELVIN and ATTR_COLOR_TEMP from the light.turn_on service call
  • Deprecate properties LightEntity.color_temp, LightEntity.min_mireds and LightEntity.max_mireds
  • Deprecate corresponding attributes LightEntity._attr_color_temp, LightEntity._attr_min_mired and LightEntity._attr_max_mired

Examples

Custom minimum/maximum color temperature

class MyLight(LightEntity):
"""Representation of a light."""

# Old
# _attr_min_mireds = 200 # 5000K
# _attr_max_mireds = 400 # 2500K

# New
_attr_min_color_temp_kelvin = 2500 # 400 mireds
_attr_max_color_temp_kelvin = 5000 # 200 mireds

Default minimum/maximum color temperature

from homeassistant.components.light import DEFAULT_MAX_KELVIN, DEFAULT_MIN_KELVIN

class MyLight(LightEntity):
"""Representation of a light."""

# Old did not need to have _attr_min_mireds / _attr_max_mireds set
# New needs to set the default explicitly
_attr_min_color_temp_kelvin = DEFAULT_MIN_KELVIN
_attr_max_color_temp_kelvin = DEFAULT_MAX_KELVIN

Dynamic minimum/maximum color temperature

from homeassistant.util import color as color_util

class MyLight(LightEntity):
"""Representation of a light."""

# Old
# def min_mireds(self) -> int:
# """Return the coldest color_temp that this light supports."""
# return device.coldest_temperature
#
# def max_mireds(self) -> int:
# """Return the warmest color_temp that this light supports."""
# return device.warmest_temperature

# New
def min_color_temp_kelvin(self) -> int:
"""Return the warmest color_temp that this light supports."""
return color_util.color_temperature_mired_to_kelvin(device.warmest_temperature)

def max_color_temp_kelvin(self) -> int:
"""Return the coldest color_temp that this light supports."""
return color_util.color_temperature_mired_to_kelvin(device.coldest_temperature)

Service call

from homeassistant.components.light import ATTR_COLOR_TEMP_KELVIN
from homeassistant.util import color as color_util

class MyLight(LightEntity):
"""Representation of a light."""
def turn_on(self, **kwargs: Any) -> None:
"""Turn on the light."""
# Old
# if ATTR_COLOR_TEMP in kwargs:
# color_temp_mired = kwargs[ATTR_COLOR_TEMP]
# color_temp_kelvin = color_util.color_temperature_mired_to_kelvin(color_temp_mired)

# Old
# if ATTR_KELVIN in kwargs:
# color_temp_kelvin = kwargs[ATTR_KELVIN]
# color_temp_mired = color_util.color_temperature_kelvin_to_mired(color_temp_kelvin)

# New
if ATTR_COLOR_TEMP_KELVIN in kwargs:
color_temp_kelvin = kwargs[ATTR_COLOR_TEMP_KELVIN]
color_temp_mired = color_util.color_temperature_kelvin_to_mired(color_temp_kelvin)

Background information

Changed name of WaterHeaterEntityDescription

· One min read

A naming error of the entity description was found in the Water Heater integration, and we have now renamed WaterHeaterEntityEntityDescription to WaterHeaterEntityDescription in the 2025.1 release. The old WaterHeaterEntityEntityDescription is deprecated, due for removal in 2026.1, and developpers are advised to use the new WaterHeaterEntityDescription instead.

See details in the core PR: #132888.

New vacuum state property

· One min read

As of Home Assistant Core 2025.1, the constants used to return state in StateVacuumEntity are deprecated and replaced by the VacuumActivity enum.

Also with this change, integrations should set the activity property instead of directly setting the state property.

There is a one-year deprecation period, and the constants will stop working from 2026.1 to ensure all custom integration authors have time to adjust.

Example


from homeassistant.components.vacuum import VacuumActivity

class MyVacuumCleaner(StateVacuumEntity):
"""My vacuum cleaner."""

@property
def activity(self) -> VacuumActivity | None:
"""Return the state of the vacuum."""
if self.device.is_cleaning():
return VacuumActivity.CLEANING
return VacuumActivity.DOCKED

More details can be found in the vacuum documentation.