# How Home Assistant Manages Integration Configuration Through Config Entries

> Discover how Home Assistant manages integration configuration using immutable ConfigEntry objects coordinated by an asynchronous state machine for seamless setup and lifecycle events.

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

---

**Home Assistant stores every integration instance in an immutable `ConfigEntry` object managed by the core module [`homeassistant/config_entries.py`](https://github.com/home-assistant/core/blob/main/homeassistant/config_entries.py), which orchestrates setup, state transitions, error recovery, and lifecycle events through an asynchronous state machine.**

The `home-assistant/core` repository uses config entries as the canonical mechanism for managing runtime integration configuration. Unlike static YAML configuration, config entries provide a programmatic, UI-driven way to add, configure, and remove integrations while persisting state across restarts.

## The ConfigEntry Data Model

### Immutable Configuration Container

At the heart of the system lies the `ConfigEntry` class defined in [`homeassistant/config_entries.py`](https://github.com/home-assistant/core/blob/main/homeassistant/config_entries.py) (lines 391-410). This dataclass serves as an immutable container holding:

- `domain`: The integration namespace (e.g., `zwave_js`, `mqtt`)
- `title`: Human-readable name shown in the UI
- `data`: Static configuration data (API keys, host addresses)
- `options`: User-modifiable preferences (polling intervals, enable/disable toggles)
- `runtime_data`: Ephemeral objects like API clients or connection sessions
- `unique_id`: Optional identifier preventing duplicate entries
- `state`: Current lifecycle stage tracked via `ConfigEntryState`

### ConfigEntryState Lifecycle States

The `ConfigEntryState` enum (config_entries.py#L47-L66) defines discrete stages including `LOADED`, `NOT_LOADED`, `SETUP_ERROR`, and `SETUP_RETRY`. Each state carries a *recoverable* flag indicating whether the entry supports unloading and reloading without a full Home Assistant restart.

## Registration and Discovery Flows

### Handler Registry

Before any integration can be configured, it must register a `ConfigFlow` subclass in the handlers registry. The core maintains this mapping at [`homeassistant/config_entries.py`](https://github.com/home-assistant/core/blob/main/homeassistant/config_entries.py) (lines 33-35):

```python
HANDLERS: Registry[str, type[ConfigFlow]] = Registry()

```

Integrations populate this registry using the decorator pattern:

```python
@config_entries.HANDLERS.register("my_integration")
class MyConfigFlow(config_entries.ConfigFlow):
    """Handle config flow for My Integration."""

```

### Discovery and User Flows

Configuration flows originate from either user interaction (`SOURCE_USER`) or automatic discovery protocols like Bluetooth, DHCP, or Zeroconf. The `DiscoveryFlowHandler` in [`homeassistant/helpers/config_entry_flow.py`](https://github.com/home-assistant/core/blob/main/homeassistant/helpers/config_entry_flow.py) (lines 30-65) provides a generic base class for these automatic flows, setting a unique ID before invoking `async_step_confirm` to create the entry.

## Setup and Migration Sequence

### Migration Phase

Before the integration loads, the core validates version compatibility by calling `async_migrate` (config_entries.py#L521-525). If migration returns `False`, the entry transitions to `MIGRATION_ERROR` and setup aborts.

### async_setup_entry Execution

The setup orchestration occurs in `ConfigEntry._async_start_setup` (config_entries.py#L556-L559), which distinguishes between platform setup (entities) and config-entry setup (integration logic). The core then:

1. Loads the integration module
2. Executes any pending migrations
3. Calls the integration's `async_setup_entry(hass, entry)` coroutine

The integration must return a boolean result, which the core validates at lines 671-676. A missing or incorrect return type triggers error logging and state transition to `SETUP_ERROR`.

```python
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
    """Set up My Integration from a config entry."""
    coordinator = MyDataUpdateCoordinator(hass, entry)
    await coordinator.async_config_entry_first_refresh()
    entry.runtime_data = coordinator
    return True

```

## Error Handling and Recovery

### Retry Logic with Exponential Back-off

When an integration raises `ConfigEntryNotReady` (indicating temporary unavailability like a offline device), the core catches the exception at lines 776-814 and schedules a retry with exponential back-off calculated at lines 814-819:

```python

# Back-off calculation example from source

wait_time = min(2 ** (entry._tries - 1), 3600)  # Cap at 1 hour

```

The entry state changes to `SETUP_RETRY` and automatically attempts setup again after the calculated interval.

### Re-authentication Flows

Authentication failures trigger a dedicated recovery path. When `ConfigEntryAuthFailed` is raised (config_entries.py#L800-803), the core invokes `async_start_reauth(hass)` to launch the config flow again, allowing users to refresh expired tokens or credentials without removing and re-adding the integration.

Other specific exceptions drive distinct state transitions:
- `ConfigEntryError` → `SETUP_ERROR` (permanent failure)
- `ConfigEntryAuthFailed` → Re-auth flow triggered
- `ConfigEntryNotReady` → `SETUP_RETRY` with back-off

## Summary

- **ConfigEntry objects** in [`homeassistant/config_entries.py`](https://github.com/home-assistant/core/blob/main/homeassistant/config_entries.py) serve as immutable containers for integration configuration, runtime data, and state.
- **Lifecycle states** (`LOADED`, `SETUP_RETRY`, etc.) managed by `ConfigEntryState` determine whether an integration can recover from failures without restart.
- **Setup orchestration** calls `async_setup_entry` after running migrations, with strict boolean result validation.
- **Error recovery** uses exception-driven state changes: `ConfigEntryNotReady` triggers exponential back-off retries, while `ConfigEntryAuthFailed` initiates re-authentication flows.
- **Discovery integration** happens through `DiscoveryFlowHandler` in [`helpers/config_entry_flow.py`](https://github.com/home-assistant/core/blob/main/helpers/config_entry_flow.py), supporting automated setup via Bluetooth, DHCP, and Zeroconf.

## Frequently Asked Questions

### What is a config entry in Home Assistant?

A config entry is an immutable runtime object that stores an integration's configuration data, options, and state. Defined in [`homeassistant/config_entries.py`](https://github.com/home-assistant/core/blob/main/homeassistant/config_entries.py) as the `ConfigEntry` class, it replaces static YAML configuration for many integrations, enabling UI-based setup and management while persisting across Home Assistant restarts.

### How does Home Assistant handle integration setup failures?

The core catches specific exceptions during `async_setup_entry` execution to determine the failure type. `ConfigEntryNotReady` triggers automatic retries with exponential back-off (config_entries.py#L814-L819), `ConfigEntryAuthFailed` starts a re-authentication flow (config_entries.py#L800-L803), and generic `ConfigEntryError` moves the entry to `SETUP_ERROR`.

### What is the difference between async_setup_entry and async_setup?

`async_setup` is the legacy initialization function for YAML-based configuration, while `async_setup_entry` is the modern entry point called for each config entry instance. The core in [`homeassistant/config_entries.py`](https://github.com/home-assistant/core/blob/main/homeassistant/config_entries.py) handles the `ConfigEntry` lifecycle separately from platform setup, allowing multiple independent instances of the same integration with different configurations.

### How do discovery flows create config entries?

Discovery handlers inherit from `DiscoveryFlowHandler` in [`helpers/config_entry_flow.py`](https://github.com/home-assistant/core/blob/main/helpers/config_entry_flow.py), which automatically sets a unique ID based on the discovered device properties and invokes `async_step_confirm`. Upon user confirmation (or automatically for ignored discoveries), the flow calls `async_create_entry`, instantiating a `ConfigEntry` that the core then persists and sets up via `async_setup_entry`.