# Ha Config Migration

> Config entry version migration in Home Assistant. Use when incrementing VERSION or MINOR_VERSION, implementing async_migrate_entry, or migrating stored entry data between schema versions.

- Skill: `l3digitalnet/ha-config-migration` (Agent Skill)
- Install (CLI): `npx skillmds@latest add l3digitalnet/ha-config-migration`
- Raw SKILL.md: https://api.skillmd.com/api/skills/l3digitalnet/ha-config-migration/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: l3digitalnet (https://skillmd.com/u/l3digitalnet)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/l3digitalnet/ha-config-migration

---


# Home Assistant — Config Entry Version Migration

When your config entry schema changes (renaming keys, adding required fields, restructuring data), increment `VERSION` and implement `async_migrate_entry`.

## Incrementing Versions

```python
# config_flow.py
class MyConfigFlow(ConfigFlow, domain=DOMAIN):
    VERSION = 2        # Increment for breaking schema changes
    MINOR_VERSION = 1  # Increment for non-breaking additions
```

## async_migrate_entry Template

```python
# __init__.py
async def async_migrate_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
    """Migrate old entry to new version."""
    _LOGGER.debug("Migrating from version %s.%s", entry.version, entry.minor_version)

    # Downgrade guard: a major version newer than this code supports means the user
    # rolled HA/the integration back. Fail cleanly — old code cannot read newer-schema
    # data, and falling through to `return True` would load it anyway (silent corruption).
    if entry.version > 2:
        return False

    if entry.version == 1:
        # Migration from v1 to v2
        new_data = {**entry.data}

        # Example: rename a key
        if "old_key" in new_data:
            new_data["new_key"] = new_data.pop("old_key")

        # Example: add new required field with default
        if "new_field" not in new_data:
            new_data["new_field"] = "default_value"

        hass.config_entries.async_update_entry(
            entry, data=new_data, version=2, minor_version=0
        )

    # async_update_entry mutates `entry` in place, so this branch sees version=2 within
    # the same call — the checks run sequentially (a 1->2->2.1 migration cascades).
    if entry.version == 2 and entry.minor_version < 1:
        # Minor version migration (non-breaking additions)
        new_options = {**entry.options}
        new_options.setdefault("new_option", True)

        hass.config_entries.async_update_entry(
            entry, options=new_options, minor_version=1
        )

    _LOGGER.info("Migration to version %s.%s successful", entry.version, entry.minor_version)
    return True
```

## Rules

- **Major VERSION** (`VERSION = 2`): Breaking changes — existing users' data must be transformed
- **MINOR_VERSION** (`MINOR_VERSION = 1`): Non-breaking additions — safe to add defaults
- Return `True` on success, `False` to signal migration failure (entry will be disabled)
- **Guard against downgrades**: if `entry.version` is greater than the latest major VERSION this code supports, `return False` at the top. A higher-major entry means the user rolled HA/the integration back; old code cannot read the newer schema, so fail the migration cleanly (entry is disabled and surfaced) rather than letting it fall through to `return True` and load corrupt data silently
- Always log migration for debuggability
- Migrate incrementally through versions (1→2→3), not directly to latest

## Related Skills

- Config flow → `ha-config-flow`
- Deprecation fixes → `ha-deprecation-fixes`

