Skip to main content

Integration quality scale

· 3 min read

Since the original quality scale was introduced in 2020, Home Assistant changed quite a lot. Both in how integrations are developed and what features they can provide. The quality scale, however, has not been updated to reflect these changes.

For the last couple of months, we have been working with the community on a new integration quality scale that fits the current state of Home Assistant. To give an overview of the changes:

  • The quality scale now has four scaled tiers: Bronze, Silver, Gold, and Platinum. The newly added Bronze tier is the new minimum requirement for new core integrations going forward. This does lower the bar for new integrations for a bit, but it does make it clear upfront what is expected from a core integration in this day and age.
  • A new non-scaled tier, legacy, has been added for integrations that are not configurable via the UI yet. This tier would indicate to the user that the integration might not meet their expectation compared to integrations configurable via the UI.
  • To allow the scale to grow with Home Assistant and to avoid another major rework in the future, we have designed a rulebook for the scale so it's known what needs to happen when a rule is added.
  • To show that the project has a commitment on keeping the scale up to date, we have created an architecture decision record (ADR) that describes the goal and the process of keeping the scale up to date.
  • The developer documentation now has a brand new section dedicated to the Integration Quality Scale. It includes the rules and the reasoning for every rule, and it provides examples that can be used to give an idea of how to implement it. There is also a checklist that can be used for changing the scale of an integration.
  • Documentation now also plays a role in the quality of the integrations, since the user experience of an integration goes beyond code.
  • To track the progress of integration, we have added a quality_scale.yaml file to the integration folder. This will contain a list of all the rules the integration is adhering to and notes of possible exemptions and the reasons behind them.

With this in place, you might wonder what the next steps are. Because this is a major rework of the scale, we have decided to drop the scale of the current integrations. This means that those integrations have to create a PR to get their scale back to the level they think it should be.

I would also like to motivate the community to help out with getting as many integrations to Bronze (or above) as possible. I also want to thank the community for their input in the quality scale and their effort in maintaining the integrations. Home Assistant would not be where it is today without the community ❤️.

New options flow properties

· 2 min read

Summary of changes

New helper properties have been added to the OptionsFlow:

  • self._config_entry_id provides the config entry ID
  • self.config_entry returns the config entry

Backwards compatibility

Until Home Assistant Core 2025.12, it is possible to set self.config_entry manually, but doing so will log a warning asking users to open an issue on the custom integration's bug tracker.

New code:

@staticmethod
@callback
def async_get_options_flow(
config_entry: ConfigEntry,
) -> OptionsFlowHandler:
"""Create the options flow."""
return OptionsFlowHandler()

class OptionsFlowHandler(OptionsFlow):
"""Options flow handler."""

def __init__(self) -> None:
"""Initialize options flow."""
self._conf_app_id: str | None = None

Old code with OptionsFlow properties:

@staticmethod
@callback
def async_get_options_flow(
config_entry: ConfigEntry,
) -> OptionsFlowHandler:
"""Create the options flow."""
return OptionsFlowHandler(config_entry)

class OptionsFlowHandler(OptionsFlow):
"""Options flow handler."""

def __init__(self, config_entry: ConfigEntry) -> None:
"""Initialize options flow."""
self.config_entry = config_entry
self._conf_app_id: str | None = None

Special handling of OptionsFlowWithConfigEntry

The main purpose of the OptionsFlowWithConfigEntry class was to provide the self.config_entry property, which is now already provided by the parent class. There are currently no plans to remove the OptionsFlowWithConfigEntry class, but it is kept for backward compatibility only and it should be avoided in new code.

Custom integrations that wish to drop references to OptionsFlowWithConfigEntry will need to consider how they are referencing self.options:

  • if self.options is not referenced, then the migration to OptionsFlow is straightforward (see PR #129651)
  • if you are only reading the options values, then it is recommended that you adjust the reads to self.config_entry.options (see PR #129895)
  • if you are updating/mutating the options values inside a single step, then it may be necessary to first copy the options (options = deepcopy(dict(self.config_entry.options)) (see PR #129928)
  • if you are updating/mutating the options values through multiple step, then it may be necessary to copy the options inside the class initialisation (self.options = deepcopy(dict(config_entry.options)) (see PR #129890)

More details can be found in the options flow documentation.

Reauth and reconfigure flows need to be linked to a config entry

· One min read

Starting a reauth or a reconfigure flow without a link to the config entry has been deprecated, and will start failing in 2025.12.

Custom integrations should be updated to trigger the reauth flow using the entry.async_start_reauth(hass) helper.

    async def async_press(self) -> None:
"""Handle the button press."""
try:
await self.device.press_button()
except DevicePasswordProtected as ex:
self.entry.async_start_reauth(self.hass)

Old incorrect code:

    async def async_press(self) -> None:
"""Handle the button press."""
try:
await self.device.press_button()
except DevicePasswordProtected as ex:
# old incorrect code:
self.hass.async_create_task(
hass.config_entries.flow.async_init(DOMAIN, context={"source": SOURCE_REAUTH}
)
)

Custom integrations can also raise a ConfigEntryAuthFailed exception during the initialization phase, or within the update method of a data update coordinator.

async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up integration from a config entry."""
username = entry.data[CONF_USERNAME]
password = entry.data[CONF_PASSWORD]

if not _credentials_valid(username, password):
raise ConfigEntryAuthFailed()

Starting a reconfigure flow is only done by the frontend and custom integrations should not need to change anything for these flows.

More details can be found in the reconfigure and reauthentication documentation.

The core config class has been moved

· One min read

Summary of changes

The definition of the core config class, an instance of which is available as hass.config has been moved from homeassistant/core.py to homeassistant/core_config.py. The move was done to make it easier to read and understand the core code. Custom integrations which currently import Config from homeassistant.core need to be updated to instead import from homeassistant.core_config.

info

Normally, integrations won't need to use the core Config class. But there's been custom integrations that have incorrect type annotations where the config object passed to the integration's async_setup is specified as a Config instance:

from homeassistant.core import Config

async def async_setup(hass: HomeAssistant, config: Config) -> bool:
"""Set up the integration."""

A correct type annotation would be like this:

from homeassistant.helpers.typing import ConfigType

async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up the integration."""

Backwards compatibility

Until Home Assistant Core 2025.11, it's possible to import from homeassistant.core, and doing so will log a warning asking users to open an issue on the custom integration's bug tracker.

Changes to the update entity

· One min read

Summary of changes

The update entity has been adjusted:

  • The in_progress property and the corresponding state attribute should now only be a bool indicating if an update is in progress, or None if unknown.
  • A new property and a corresponding state attribute update_percentage has been added which can either return an int or float indicating the progress from 0 to 100% or None.
  • A new property and a corresponding state attribute display_precision has been added to control the number of decimals to display in the frontend when update_percentage is a float.

Backwards compatibility

Until Home Assistant Core 2025.12, a numerical value in the in_progress property will be automatically copied to the update_percentage state attribute.

Documentation and core implementation

See the update entity developer documentation for details.

PRs:

New alarm control panel state property and state enum

· One min read

As of Home Assistant Core 2024.11, we have introduced the alarm_state property in the AlarmControlPanelEntity. This newly added property should be used instead of directly setting the state property.

The new alarm_state property should return its state using the new AlarmControlPanelState enum instead of as previously, setting the state using the STATE_ALARM_* constants.

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

Example


from homeassistant.components.alarm_control_panel import AlarmControlPanelEntity, AlarmControlPanelState

class MyAlarm(AlarmControlPanelEntity):
"""My alarm."""

@property
def alarm_state(self) -> AlarmControlPanelState | None:
"""Return the state of the alarm."""
if self.device.is_on():
return AlarmControlPanelState.ARMED_AWAY
return AlarmControlPanelState.DISARMED

More details can be found in the alarm control panel documentation.

New helpers and best practises for reauth and reconfigure flows

· 2 min read

New helper methods have been added to the ConfigFlow to facilitate management of reauth and reconfigure flows:

  • self._get_reauth_entry() and self._get_reconfigure_entry() give access at any time to the corresponding config entry
    • these should be used over self.hass.config_entries.async_get_entry(self.context["entry_id"])
    • the config entry should be requested when needed (local variable, once per step) and not cached as class attributes
    • if the steps are shared with discovery or user flows, self.source should be checked against SOURCE_REAUTH and SOURCE_RECONFIGURE before accessing the entry
  • self._abort_if_unique_id_mismatch allows you to abort if the unique_id does not match the unique_id of the config entry to reauthenticate or reconfigure
    • this should be used after a call to self.async_set_unique_id
    • if the steps are shared with discovery or user flows, self.source should be checked against SOURCE_REAUTH and SOURCE_RECONFIGURE
    • other sources should continue to use self._abort_if_unique_id_configured
  • self.async_update_reload_and_abort has been adjusted to update the default message for reconfigure flows
    • the new message reconfigure_successful must be present in strings.json
  • self.async_update_reload_and_abort has a new argument data_updates to merge the data updates with the pre-existing data
    • this is preferred over the data argument, as it reduces the risk of data loss if the schema is updated

More details can be found in the reconfigure and reauthentication documentation.

Extend deprecation period of hass.helpers

· One min read

On March 30, 2024, we announced the deprecation of the hass.helpers attribute for the Home Assistant 2024.11 release. Due to the large number of custom integrations that still use them and the recent HACS v2 update, we have decided to extend the deprecation period for another six months.

This means that starting with Home Assistant 2025.5, hass.helpers will be removed.

We encourage all developers of custom integrations to update their code to avoid any issues prior to the Home Assistant 2025.5 release.

Deprecating state constants for cover

· One min read

As of Home Assistant Core 2024.11, the constants used to return state in CoverEntity are deprecated and replaced by the CoverState enum.

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

As the state property is not meant to be overwritten, in most cases this change will only affect other Entity properties or tests rather than the state property.

More details can be found in the cover documentation.

Changes to the UnitOfConductivity enum

· One min read

The UnitOfConductivity enum has been changed from:

  class UnitOfConductivity(StrEnum):
"""Conductivity units."""

SIEMENS = "S/cm"
MICROSIEMENS = "µS/cm"
MILLISIEMENS = "mS/cm"

To:

  class UnitOfConductivity(StrEnum):
"""Conductivity units."""

SIEMENS_PER_CM = "S/cm"
MICROSIEMENS_PER_CM = "µS/cm"
MILLISIEMENS_PER_CM = "mS/cm"

The old enum members can be used during a deprecation period of one year, to give time for custom integrations to migrate to the new enum members.

See core PR #127919 for implementation details.