# Understanding Home Assistant's Data Entry Flow System and Config Flows

> Explore Home Assistant's data entry flow system and config flows. Learn how the async framework and ConfigFlow subclasses guide user setup and integration configuration for a seamless experience.

- Repository: [Home Assistant/core](https://github.com/home-assistant/core)
- Tags: deep-dive
- Published: 2026-02-28

---

**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`](https://github.com/home-assistant/core/blob/main/homeassistant/data_entry_flow.py) and [`homeassistant/config_entries.py`](https://github.com/home-assistant/core/blob/main/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 in [`homeassistant/data_entry_flow.py`](https://github.com/home-assistant/core/blob/main/homeassistant/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 in [`homeassistant/config_entries.py`](https://github.com/home-assistant/core/blob/main/homeassistant/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 in [`config_entries.py`](https://github.com/home-assistant/core/blob/main/config_entries.py) (lines ~2910-2930), this provides shared functionality for both config and options flows, including the `_async_abort_entries_match` method 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`](https://github.com/home-assistant/core/blob/main/homeassistant/data_entry_flow.py):

1. **Registration** – Developers subclass `ConfigFlow` with the optional `domain` argument; the `__init_subclass__` method automatically registers the handler in the global `HANDLERS` registry.
2. **Flow Initialization** – The UI or automatic discovery calls `hass.config_entries.flow.async_init(domain, ...)`, generating a unique `flow_id` and instantiating a `FlowHandler` instance.
3. **Step Execution** – The flow manager calls the initial step method (defaulting to `async_step_init`), which typically redirects to `async_step_user`.
4. **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_result` method (lines 890-906).
5. **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_match` or `_abort_if_unique_id_mismatch`.
6. **Entry Creation** – Successful validation calls `self.async_create_entry(title="...", data={...})`, persisting a `ConfigEntry` to `core.config_entries`.
7. **Options Flow** – For post-setup configuration, `async_get_options_flow` returns an `OptionsFlow` subclass 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`](https://github.com/home-assistant/core/blob/main/homeassistant/data_entry_flow.py) provides the foundational methods that all configuration wizards inherit:

```python
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`](https://github.com/home-assistant/core/blob/main/homeassistant/config_entries.py) (lines 2918-2950), it handles automatic registration via `__init_subclass__`:

```python
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`](https://github.com/home-assistant/core/blob/main/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`](https://github.com/home-assistant/core/blob/main/homeassistant/components/example/config_flow.py):

```python
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=DOMAIN` class argument triggers automatic registration via `ConfigFlow.__init_subclass__`.
- **Step Methods** – `async_step_user` handles the initial user interaction, returning `async_show_form` when awaiting input and `async_create_entry` upon successful validation.
- **Schema Validation** – Voluptuous schemas define the form structure and validation rules sent to the frontend.
- **Duplicate Checking** – `_async_abort_entries_match` ensures users cannot add the same API key twice.
- **Error Handling** – Returning `async_show_form` with an `errors` dictionary displays validation failures to the user.

## Summary

- The **data entry flow system** provides the generic engine (`FlowHandler`) for multi-step configuration wizards in [`homeassistant/data_entry_flow.py`](https://github.com/home-assistant/core/blob/main/homeassistant/data_entry_flow.py).
- **Config flows** are implemented by subclassing `ConfigFlow` in [`homeassistant/config_entries.py`](https://github.com/home-assistant/core/blob/main/homeassistant/config_entries.py), which handles domain registration and unique ID management.
- The framework uses `async_show_form` to render UI steps and `async_create_entry` to persist configuration, with built-in helpers like `_async_abort_entries_match` preventing duplicates.
- All configuration entries are stored as `ConfigEntry` objects 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`](https://github.com/home-assistant/core/blob/main/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`](https://github.com/home-assistant/core/blob/main/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`](https://github.com/home-assistant/core/blob/main/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`](https://github.com/home-assistant/core/blob/main/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`.