Prevent duplicate config entries
Bronze rule unique-config-entry: the same device or service cannot be set up twice.
This is a Bronze rule. In quality_scale.yaml, it is called unique-config-entry.
The config flow must stop the user from setting up the same device or service twice. Every flow, including discovery flows, aborts when the device or service already has a config entry.
Setting up an integration from the UI is easy, so setting it up twice by accident is easy too. Two entries for the same device mean duplicate devices and entities, and unique IDs that collide. Discovery makes it worse: without a way to recognize a known device, Home Assistant keeps offering to set it up again.
There are two ways to do this.
Give the config entry a unique ID, like the serial number of the device, and abort when an entry with that ID exists. This is the preferred way, because it also lets discovery recognize the device:
async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle a flow started by the user."""
errors: dict[str, str] = {}
if user_input is not None:
client = MyClient(user_input[CONF_HOST])
try:
info = await client.get_device_info()
except MyConnectionError:
errors["base"] = "cannot_connect"
else:
await self.async_set_unique_id(info.serial_number)
self._abort_if_unique_id_configured()
return self.async_create_entry(title=info.name, data=user_input)
return self.async_show_form(
step_id="user",
data_schema=probatio.Schema({probatio.Required(CONF_HOST): str}),
errors=errors,
)The unique ID only has to be unique within your integration. Use a stable hardware identifier, like a serial number or a MAC address formatted with format_mac. Do not use an IP address or hostname: those change.
When the device has no identifier to use, compare the data of the existing entries instead:
if user_input is not None:
self._async_abort_entries_match({CONF_HOST: user_input[CONF_HOST]})The flow aborts with already_configured when an entry with the same host exists.
- Hassfest, when the rule is
done: the config flow must call_async_abort_entries_match, or bothasync_set_unique_idand_abort_if_unique_id_configured. Integrations withsingle_config_entryin their manifest pass this check. - Pylint, for every integration:
home-assistant-unique-id-ip-basedwarns whenasync_set_unique_idgets an IP address or hostname. - Pylint:
home-assistant-tests-config-flow-unique-idwarns when a config flow test creates an entry without checking its unique ID. - The test of the duplicate case is part of config-flow-test-coverage.
Integrations that only support a single config entry are exempt from this rule. Usually, that is set with "single_config_entry": true in manifest.json.
- config-flow: set up from the UI with a config flow.
- test-before-configure: test the connection in the config flow.
- config-flow-test-coverage: test the config flow fully.
- discovery: support discovery.
- reauthentication-flow: reauthenticate from the UI.
- reconfiguration-flow: reconfigure from the UI.