Integration Configuration

Integrations can be set up via the user interface by adding support for a config flow to create a config entry. Components that want to support config entries will need to define a Config Flow Handler. This handler will manage the creation of entries from user input, discovery or other sources (like Home Assistant OS).

Config Flow Handlers control the data that is stored in a config entry. This means that there is no need to validate that the config is correct when Home Assistant starts up. It will also prevent breaking changes, because we will be able to migrate configuration entries to new formats if the version changes.

When instantiating the handler, Home Assistant will make sure to load all dependencies and install the requirements of the component.

Updating the manifest#

You need to update your integrations manifest to inform Home Assistant that your integration has a config flow. This is done by adding config_flow: true to your manifest (docs).

Defining your config flow#

Config entries uses the data flow entry framework to define their config flows. The config flow needs to be defined in the file in your integration folder, extend homeassistant.config_entries.ConfigFlow and pass a domain key as part of inheriting ConfigFlow.

from homeassistant import config_entries
from .const import DOMAIN
class ExampleConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Example config flow."""

Once you have updated your manifest and created the, you will need to run python3 -m script.hassfest (one time only) for Home Assistant to activate the config entry for your integration.

Defining steps#

Your config flow will need to define steps of your configuration flow. The docs for Data Entry Flow describe the different return values of a step. Here is an example on how to define the user step.

import voluptuous as vol
class ExampleConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
async def async_step_user(self, info):
if info is not None:
pass # TODO: process info
return self.async_show_form(
step_id="user", data_schema=vol.Schema({vol.Required("password"): str})

There are a few step names reserved for system use:

Step nameDescription
discoveryDEPRECATED Invoked if your integration has been discovered by the discovery integration.
dhcpInvoked if your integration has been discovered via DHCP as specified using dhcp in the manifest.
homekitInvoked if your integration has been discovered via HomeKit as specified using homekit in the manifest.
mqttInvoked if your integration has been discovered via MQTT as specified using mqtt in the manifest.
ssdpInvoked if your integration has been discovered via SSDP/uPnP as specified using ssdp in the manifest.
userInvoked when a user initiates a flow via the user interface.
zeroconfInvoked if your integration has been discovered via Zeroconf/mDNS as specified using zeroconf in the manifest.

Unique IDs#

A config flow can attach a unique ID to a config flow to avoid the same device being set up twice. When a unique ID is set, it will immediately abort if another flow is in progress for this unique ID. You can also quickly abort if there is already an existing config entry for this ID. Config entries will get the unique ID of the flow that creates them.

Call inside a config flow step:

await self.async_set_unique_id(device_unique_id)

By setting a unique ID, users will have the option to ignore the discovery of your config entry. That way, they won't be bothered about it anymore. If the integration uses DHCP, HomeKit, Zeroconf/mDNS or SSDP/uPnP to be discovered, supplying a unique ID is required.

If a unique ID isn't available, alternatively, the dhcp, zeroconf, homekit, ssdp and discovery steps can be omitted, even if they are configured in the integration manifest. In that case, the user step will be called when the item is discovered.

Alternatively, if an integration can't get a unique ID all the time (e.g., multiple devices, some have one, some don't), a helper is available that still allows for discovery, as long as there aren't any instances of the integrations configured yet.

if device_unique_id:
await self.async_set_unique_id(device_unique_id)
await self._async_handle_discovery_without_unique_id()


Your configuration flow can add support to re-discover the previously ignored entry by implementing the unignore step in the config flow.

async def async_step_unignore(self, user_input):
unique_id = user_input["unique_id"]
await self.async_set_unique_id(unique_id)
# TODO: Discover devices and find the one that matches the unique ID.
return self.async_show_form()

Discovery steps#

When an integration is discovered, their respective discovery step is invoked with the discovery information. The step will have to check the following things:

  • Make sure there are no other instances of this config flow in progress of setting up the discovered device. This can happen if there are multiple ways of discovering that a device is on the network.
  • Make sure that the device is not already set up.
  • Invoking a discovery step should never result in a finished flow and a config entry. Always confirm with the user.

Discoverable integrations that require no authentication#

If your integration is discoverable without requiring any authentication, you'll be able to use the Discoverable Flow that is built-in. This flow offers the following features:

  • Detect if devices/services can be discovered on the network before finishing the config flow.
  • Support all manifest-based discovery protocols.
  • Limit to only 1 config entry. It is up to the config entry to discover all available devices.

To get started, run python3 -m script.scaffold config_flow_discovery and follow the instructions. This will create all the boilerplate necessary to configure your integration using discovery.

Configuration via OAuth2#

Home Assistant has built-in support for integrations that offer account linking using the OAuth2 authorization framework. To be able to leverage this, you will need to structure your Python API library in a way that allows Home Assistant to be responsible for refreshing tokens. See our API library guide on how to do this.

The built-in OAuth2 support works out of the box with locally configured client ID / secret and with the Home Assistant Cloud Account Linking service. This service allows users to link their account with a centrally managed client ID/secret. If you want your integration to be part of this service, reach out to us at [email protected].

To get started, run python3 -m script.scaffold config_flow_oauth2 and follow the instructions. This will create all the boilerplate necessary to configure your integration using OAuth2.


Translations for the config flow handlers are defined under the config key in the component translation file strings.json. Example of the Hue component:

"title": "Philips Hue Bridge",
"config": {
"step": {
"init": {
"title": "Pick Hue bridge",
"data": {
"host": "Host"
"link": {
"title": "Link Hub",
"description": "Press the button on the bridge to register Philips Hue with Home Assistant.\n\n![Location of button on bridge](/static/images/config_philips_hue.jpg)"
"error": {
"register_failed": "Failed to register, please try again",
"linking": "Unknown linking error occurred."
"abort": {
"discover_timeout": "Unable to discover Hue bridges",
"no_bridges": "No Philips Hue bridges discovered",
"all_configured": "All Philips Hue bridges are already configured",
"unknown": "Unknown error occurred",
"cannot_connect": "Unable to connect to the bridge",
"already_configured": "Bridge is already configured"

When the translations are merged into Home Assistant, they will be automatically uploaded to Lokalise where the translation team will help to translate them in other languages. While developing locally, you will need to run python3 -m script.translations develop to see changes made to strings.json More info on translating Home Assistant.

Config Entry Migration#

As mentioned above - each Config Entry has a version assigned to it. This is to be able to migrate Config Entry data to new formats when Config Entry schema changes.

Migration can be handled programatically by implementing function async_migrate_entry in your component's file. The function should return True if migration is successfull.

# Example migration function
async def async_migrate_entry(hass, config_entry: ConfigEntry):
"""Migrate old entry."""
_LOGGER.debug("Migrating from version %s", config_entry.version)
if config_entry.version == 1:
new = {**}
# TODO: modify Config Entry data = {**new}
config_entry.version = 2"Migration to version %s successful", config_entry.version)
return True


Gracefully handling authentication errors such as invalid, expired, or revoked tokens is needed to advance on the Integration Qualily Scale. This example of how to add reauth to the OAuth flow created by script.scaffold following the pattern in Building a Python library.

This example catches an authentication exception in config entry setup in and instructs the user to visit the integrations page in order to reconfigure the integration.

from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntry
from homeassistant.core import HomeAssistant
from . import api
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
"""Setup up a config entry."""
# TODO: Replace with actual API setup and exception
auth = api.AsyncConfigEntryAuth(...)
await auth.refresh_tokens()
except TokenExpiredError:
context={"source": SOURCE_REAUTH},,
return False
# TODO: Proceed with component setup

The flow handler in also needs to have some additional steps to support reauth which include showing a confirmation, starting the reauth flow, updating the existing config entry, and reloading to invoke setup again.

class OAuth2FlowHandler(
config_entry_oauth2_flow.AbstractOAuth2FlowHandler, domain=DOMAIN
"""Config flow to handle OAuth2 authentication."""
async def async_step_reauth(self, user_input=None):
"""Perform reauth upon an API authentication error."""
return await self.async_step_reauth_confirm()
async def async_step_reauth_confirm(self, user_input=None):
"""Dialog that informs the user that reauth is required."""
if user_input is None:
return self.async_show_form(
return await self.async_step_user()
async def async_oauth_create_entry(self, data: dict) -> dict:
"""Create an oauth config entry or update existing entry for reauth."""
# TODO: This example supports only a single config entry. Consider
# any special handling needed for multiple config entries.
existing_entry = await self.async_set_unique_id(DOMAIN)
if existing_entry:
self.hass.config_entries.async_update_entry(existing_entry, data=data)
await self.hass.config_entries.async_reload(existing_entry.entry_id)
return self.async_abort(reason="reauth_successful")
return await super().async_oauth_create_entry(data)

Depending on the details of the integration, there may be additional considerations such as ensuring the same account is used across reauth, or handling multiple config entries.

The reauth confirmation dialog needs additional definitions in strings.json for the reauth confirmation and success diaglogs:

"config": {
"step": {
"reauth_confirm": {
"title": "[%key:common::config_flow::title::reauth%]",
# TODO: Replace with the name of the integration
"description": "The Example integration needs to re-authenticate your account"
"abort": {
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"

See Translations local development instructions.

Authentication failures (such as a revoked oauth token) can be a little tricky to manually test. One suggestion is to make a copy of config/.storage/core.config_entries and manually change the values of access_token, refresh_token, and expires_at depending on the scenario you want to test. You can then walk advance through the reauth flow and confirm that the values get replaced with new valid tokens.

Automated tests should verify that the reauth flow updates the existing config entry and does not create additional entries.

Last updated on