Understanding Home Assistant's Data Entry Flow System and Config Flows
The data entry flow system is an asynchronous framework that guides users through integration setup using the generic FlowHandler class, while domain-specific subclasses of ConfigFlow implement the actual validation, form presentation, and configuration entry creation logic.
Home Assistant's extensible architecture relies on a robust data entry flow system to handle the configuration of integrations, devices, and services through the UI. This framework decouples the generic wizard mechanics from integration-specific logic, allowing developers to create guided setup experiences by subclassing ConfigFlow. The core implementation resides in homeassistant/data_entry_flow.py and homeassistant/config_entries.py, providing the foundation for all UI-driven configuration in the platform.
Architecture of the Data Entry Flow System
The data entry flow system separates concerns between the generic flow engine and integration-specific configuration logic. Understanding these boundaries is essential for developing custom integrations.
Core Components and Responsibilities
The system consists of three primary classes defined across the core repository:
FlowHandler– Located inhomeassistant/data_entry_flow.py(lines 609-785), this generic engine manages flow instances, stores context, and handles form rendering, entry creation, and aborts.ConfigFlow– Defined inhomeassistant/config_entries.py(lines 2918-2950), this base class registers handlers by domain and adds integration-specific helpers for unique ID handling and options flow support.ConfigEntryBaseFlow– Also inconfig_entries.py(lines ~2910-2930), this provides shared functionality for both config and options flows, including the_async_abort_entries_matchmethod for duplicate detection.
How Config Flows Work Step-by-Step
The lifecycle of a config flow follows a structured seven-step process managed by the flow manager in homeassistant/data_entry_flow.py:
- Registration – Developers subclass
ConfigFlowwith the optionaldomainargument; the__init_subclass__method automatically registers the handler in the globalHANDLERSregistry. - Flow Initialization – The UI or automatic discovery calls
hass.config_entries.flow.async_init(domain, ...), generating a uniqueflow_idand instantiating aFlowHandlerinstance. - Step Execution – The flow manager calls the initial step method (defaulting to
async_step_init), which typically redirects toasync_step_user. - Form Presentation – The flow returns
self.async_show_form(step_id="user", data_schema=vol.Schema(...)), serializing the form definition to the frontend via the_async_flow_handler_to_flow_resultmethod (lines 890-906). - Input Processing – Upon submission, the flow receives user data, performs async validation (e.g., testing API connections), and checks for duplicates using
_async_abort_entries_matchor_abort_if_unique_id_mismatch. - Entry Creation – Successful validation calls
self.async_create_entry(title="...", data={...}), persisting aConfigEntrytocore.config_entries. - Options Flow – For post-setup configuration,
async_get_options_flowreturns anOptionsFlowsubclass that follows the same step pattern for modifying existing entries.
Core API Implementation Details
The config flow system exposes specific APIs for form rendering, validation, and state management. These implementations demonstrate the framework's asynchronous, step-driven design.
The FlowHandler Base Class
The FlowHandler class in homeassistant/data_entry_flow.py provides the foundational methods that all configuration wizards inherit:
class FlowHandler(Generic[_FlowContextT, _FlowResultT, _HandlerT]):
"""Handle a data entry flow."""
cur_step: _FlowResultT | None = None
flow_id: str = None
hass: HomeAssistant = None
handler: _HandlerT = None
context: _FlowContextT = MappingProxyType({})
@property
def source(self) -> str | None:
"""Source that initialized the flow."""
return self.context.get("source", None)
def async_show_form(...):
"""Return a FORM result for the UI."""
...
def async_create_entry(...):
"""Finish flow and create a ConfigEntry."""
...
def async_abort(...):
"""Abort the flow."""
...
Key methods include async_show_form() for rendering UI steps, async_create_entry() for persisting configuration, and async_abort() for terminating flows with specific error codes.
ConfigFlow Integration Interface
The ConfigFlow class extends the base functionality with integration-specific utilities. According to the source in homeassistant/config_entries.py (lines 2918-2950), it handles automatic registration via __init_subclass__:
class ConfigFlow(ConfigEntryBaseFlow):
"""Base class for config flows with some helpers."""
def __init_subclass__(cls, *, domain: str | None = None, **kwargs):
super().__init_subclass__(**kwargs)
if domain is not None:
HANDLERS.register(domain)(cls) # ← registers the flow
@property
def unique_id(self) -> str | None:
"""Return unique ID if available."""
if not self.context:
return None
return self.context.get("unique_id")
@staticmethod
@callback
def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
"""Get the options flow for this handler."""
raise data_entry_flow.UnknownHandler
The unique_id property enables the system to identify existing entries and prevent duplicates across the integration domain.
Duplicate Prevention Helpers
Config flows utilize specialized abort methods defined in config_entries.py (lines ~2890-2898) to maintain data integrity:
_async_abort_entries_match– Aborts the flow when another entry already contains identical data._abort_if_unique_id_mismatch– Aborts if the flow's supplied unique ID does not match an existing entry's identifier.
Both helpers raise data_entry_flow.AbortFlow("already_configured") to signal the frontend that the device or service is already configured.
Building a Custom Config Flow: Practical Example
Implementing a config flow requires subclassing ConfigFlow and defining step methods that handle user interaction. Below is a minimal implementation pattern from homeassistant/components/example/config_flow.py:
from homeassistant import config_entries, data_entry_flow
import voluptuous as vol
DOMAIN = "example"
class ExampleConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Config flow for the Example integration."""
VERSION = 1
async def async_step_user(self, user_input: dict | None = None):
"""First step of the flow (shown to the user)."""
if user_input is None:
# Show a form asking for an API key
return self.async_show_form(
step_id="user",
data_schema=vol.Schema(
{
vol.Required("api_key"): str,
}
),
errors={},
)
# Validate the API key (async I/O)
try:
await self._test_api_key(user_input["api_key"])
except Exception:
return self.async_show_form(
step_id="user",
data_schema=vol.Schema({vol.Required("api_key"): str}),
errors={"api_key": "invalid"},
)
# No duplicate entries?
await self._async_abort_entries_match(
match_dict={"api_key": user_input["api_key"]}
)
# Create the entry
return self.async_create_entry(
title="Example Integration",
data=user_input,
)
async def _test_api_key(self, api_key: str) -> None:
"""Placeholder for real async validation of the key."""
# Perform network request here...
return
Key implementation points include:
- Domain Registration – The
domain=DOMAINclass argument triggers automatic registration viaConfigFlow.__init_subclass__. - Step Methods –
async_step_userhandles the initial user interaction, returningasync_show_formwhen awaiting input andasync_create_entryupon successful validation. - Schema Validation – Voluptuous schemas define the form structure and validation rules sent to the frontend.
- Duplicate Checking –
_async_abort_entries_matchensures users cannot add the same API key twice. - Error Handling – Returning
async_show_formwith anerrorsdictionary displays validation failures to the user.
Summary
- The data entry flow system provides the generic engine (
FlowHandler) for multi-step configuration wizards inhomeassistant/data_entry_flow.py. - Config flows are implemented by subclassing
ConfigFlowinhomeassistant/config_entries.py, which handles domain registration and unique ID management. - The framework uses
async_show_formto render UI steps andasync_create_entryto persist configuration, with built-in helpers like_async_abort_entries_matchpreventing duplicates. - All configuration entries are stored as
ConfigEntryobjects and managed through the config entry registry, supporting post-setup modification via options flows.
Frequently Asked Questions
What is the difference between FlowHandler and ConfigFlow?
FlowHandler is the generic base class in homeassistant/data_entry_flow.py that manages the flow lifecycle and UI communication, while ConfigFlow is the integration-specific subclass in homeassistant/config_entries.py that adds domain registration, unique ID handling, and options flow support for Home Assistant integrations.
How does a config flow prevent duplicate entries?
Config flows use helper methods such as _async_abort_entries_match and _abort_if_unique_id_mismatch, implemented in homeassistant/config_entries.py around lines 2890-2898, to check existing entries against the flow's unique ID or data. If a match is found, the flow raises AbortFlow("already_configured") to prevent duplicates.
Can a config flow have multiple steps?
Yes, config flows support multi-step wizards by defining methods named async_step_<step_id>. The flow returns self.async_show_form(step_id="next_step", ...) to progress to subsequent steps, with the flow manager in homeassistant/data_entry_flow.py handling the state transitions between steps.
What triggers a config flow to start?
Config flows are initiated when the UI calls hass.config_entries.flow.async_init(domain, ...) with the integration's domain identifier, or when automatic discovery mechanisms (like Zeroconf or SSDP) trigger the creation of a flow instance with a generated flow_id.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →