# Understanding hass.data, runtime_data, and Config Entry Data in Home Assistant

> Discover the relationship between hass.data, runtime_data, and config entry data in Home Assistant. Understand how these elements manage persistent configurations and live integration objects for seamless operation.

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

---

**In Home Assistant, `entry.data` stores persistent user configuration, `entry.runtime_data` holds live integration objects during runtime, and `hass.data` provides a global dictionary for cross-platform access to those same objects.**

Home Assistant Core uses a three-tier architecture to manage integration state across restarts and entity platforms. Understanding the distinction between **config entry data**, **runtime data**, and the global **`hass.data`** registry is essential for developing integrations that properly persist settings and share live clients or coordinators with multiple entities.

## The Three Data Storage Layers

### Config Entry Data (entry.data)

The `data` attribute of a `ConfigEntry` object contains the static configuration provided by the user during setup—values like IP addresses, API tokens, scan intervals, or authentication credentials. This data is **persisted to disk** in Home Assistant's [`config_entries.json`](https://github.com/home-assistant/core/blob/main/config_entries.json) storage and remains immutable during runtime.

According to the source in [`homeassistant/config_entries.py`](https://github.com/home-assistant/core/blob/main/homeassistant/config_entries.py), this dictionary is loaded at startup and used to reconstruct integrations. Modifications require calling `hass.config_entries.async_update_entry()`, which triggers a reload and rebuilds runtime objects from the new configuration.

### Runtime Data (entry.runtime_data)

Introduced to standardize how integrations store live objects, **`entry.runtime_data`** holds transient, integration-specific instances like API clients, WebSocket connections, or `DataUpdateCoordinator` objects. This attribute is **not persisted**—it is initialized during `async_setup_entry` and destroyed when the entry is unloaded.

In [`homeassistant/components/zwave_js/__init__.py`](https://github.com/home-assistant/core/blob/main/homeassistant/components/zwave_js/__init__.py), the integration constructs a `ZwaveJSData` dataclass containing the client and driver events, then assigns it directly to the entry:

```python
entry_runtime_data = ZwaveJSData(client=client, driver_events=driver_events)
entry.runtime_data = entry_runtime_data

```

This pattern allows entities to access live objects via `config_entry.runtime_data` without importing global state.

### Global Storage (hass.data)

The **`hass.data`** dictionary serves as a global registry where integrations can store arbitrary data under their domain key. While `runtime_data` is the modern canonical location for entry-specific objects, many integrations mirror the same data into `hass.data[DOMAIN][entry.entry_id]` to facilitate access from entity platforms that receive `hass` and `entry_id` but not the full `ConfigEntry` instance.

In [`homeassistant/components/zwave_me/__init__.py`](https://github.com/home-assistant/core/blob/main/homeassistant/components/zwave_me/__init__.py), the controller is stored in both locations:

```python
hass.data.setdefault(DOMAIN, {})
controller = hass.data[DOMAIN][entry.entry_id] = ZWaveMeController(hass, entry)

```

Similarly, [`homeassistant/components/youtube/__init__.py`](https://github.com/home-assistant/core/blob/main/homeassistant/components/youtube/__init__.py) stores a dictionary containing the coordinator under `hass.data[DOMAIN][entry.entry_id]`, allowing sensor platforms to retrieve it during entity setup:

```python
coordinator = hass.data[DOMAIN][entry.entry_id][COORDINATOR]

```

## How the Layers Interact During Lifecycle

### Setup Flow

When Home Assistant loads an integration, it executes `async_setup_entry` with the persisted `ConfigEntry`. The typical workflow follows this pattern:

1. **Read static config** from `entry.data` (host, API key, etc.)
2. **Initialize live objects** (clients, coordinators) using that config
3. **Store in runtime_data** by assigning to `entry.runtime_data`
4. **Mirror to hass.data** (optional) under `hass.data[DOMAIN][entry.entry_id]` for platform access
5. **Forward entry** to entity platforms via `async_forward_entry_setups`

### Update and Reload

When configuration changes (e.g., user updates a setting via the UI), Home Assistant calls `async_update_entry`, which:

1. Writes new values to `entry.data` and persists to JSON
2. Triggers an entry reload (unload + setup)
3. Destroys the old `runtime_data` and `hass.data` entries
4. Rebuilds live objects from the new `entry.data`

```python
await hass.config_entries.async_update_entry(
    entry,
    data={**entry.data, CONF_SCAN_INTERVAL: 30},
)

```

### Unload

During `async_unload_entry`, integrations must clean up `hass.data` to prevent memory leaks, while `runtime_data` is automatically discarded by the ConfigEntry machinery:

```python

# Cleanup pattern in async_unload_entry

hass.data[DOMAIN].pop(entry.entry_id)

```

## Key Implementation Files

| File | Purpose |
|------|---------|
| [`homeassistant/config_entries.py`](https://github.com/home-assistant/core/blob/main/homeassistant/config_entries.py) | Defines `ConfigEntry` class with `data` and `runtime_data` attributes; implements persistence and reload logic. |
| [`homeassistant/components/zwave_js/__init__.py`](https://github.com/home-assistant/core/blob/main/homeassistant/components/zwave_js/__init__.py) | Demonstrates modern `runtime_data` pattern using a dataclass to store client and driver events. |
| [`homeassistant/components/zwave_me/__init__.py`](https://github.com/home-assistant/core/blob/main/homeassistant/components/zwave_me/__init__.py) | Shows legacy `hass.data` storage pattern for controller access across platforms. |
| [`homeassistant/components/youtube/__init__.py`](https://github.com/home-assistant/core/blob/main/homeassistant/components/youtube/__init__.py) | Illustrates hybrid approach storing coordinator in `hass.data` dictionary for entity retrieval. |

## Summary

- **`entry.data`** contains persisted user configuration (host, credentials, options) that survives restarts and is stored in JSON.
- **`entry.runtime_data`** holds live, non-persisted objects (API clients, coordinators) created during setup; it is the modern canonical location for entry-specific runtime state.
- **`hass.data`** provides a global dictionary for cross-platform access, often mirroring the same objects stored in `runtime_data` to facilitate entity setup without passing ConfigEntry instances.
- Updates flow through `async_update_entry`, which modifies `entry.data`, triggers a reload, and rebuilds runtime objects from the new configuration.

## Frequently Asked Questions

### What is the difference between entry.data and entry.runtime_data?

`entry.data` stores static configuration values like IP addresses and API keys that are written to disk and reloaded on startup. `entry.runtime_data` stores live objects like API clients or data coordinators that exist only while Home Assistant is running and are recreated during the setup phase. While `entry.data` persists across restarts, `entry.runtime_data` is ephemeral and destroyed when the integration unloads.

### When should I use hass.data instead of runtime_data?

Use `hass.data` when entity platforms need to access integration-specific objects but only receive `hass` and `entry_id` during setup, not the full `ConfigEntry` instance. This pattern allows platforms to retrieve coordinators or controllers via `hass.data[DOMAIN][entry_id]`. However, if entities have direct access to the `ConfigEntry` object, prefer `entry.runtime_data` as it provides type safety and avoids global state pollution.

### How do I update configuration values for a config entry?

Modify `entry.data` by calling `hass.config_entries.async_update_entry(entry, data=new_data)`. This method persists the new configuration to JSON storage and automatically triggers a config entry reload, which unloads the current runtime objects and rebuilds them from the updated configuration. Never mutate `entry.data` directly, as this bypasses persistence and reload logic.

### What happens to runtime_data when an integration reloads?

When a configuration entry reloads (either manually or via `async_update_entry`), Home Assistant calls `async_unload_entry`, which destroys the current `runtime_data` object. The integration then re-executes `async_setup_entry`, creating a fresh `runtime_data` instance from the current `entry.data`. This ensures that runtime objects always reflect the latest configuration and that no stale connections or state persist across reloads.