Home Assistant Integration Scaffolding
Required File Structure
custom_components/{domain}/
├── __init__.py # Entry point: async_setup_entry, async_unload_entry
├── manifest.json # Integration metadata (REQUIRED)
├── config_flow.py # UI-based configuration (REQUIRED)
├── const.py # Domain constant, platform list
├── coordinator.py # DataUpdateCoordinator subclass
├── entity.py # Base entity class (recommended)
├── strings.json # Config flow strings
├── services.yaml # Service action definitions (if registering services)
├── icons.json # Entity and service icons (optional)
├── translations/
│ └── en.json # English translations
└── [platform].py # One per entity platform (sensor.py, switch.py, etc.)
For HACS distribution, also include at repository root:
/
├── custom_components/{domain}/ # Integration files
├── hacs.json # HACS metadata (REQUIRED for HACS)
├── README.md # Documentation
└── LICENSE # License file
manifest.json (2025 Requirements)
{
"domain": "{domain_name}",
"name": "{Human Readable Name}",
"version": "1.0.0",
"codeowners": ["@{github_username}"],
"config_flow": true,
"dependencies": [],
"documentation": "https://github.com/{user}/{repo}",
"integration_type": "hub",
"iot_class": "local_polling",
"issue_tracker": "https://github.com/{user}/{repo}/issues",
"requirements": []
}
Always required:
domain: lowercase, underscores only, matches folder name
name: human-readable
codeowners: GitHub usernames with @ prefix
documentation: URL to integration docs
iot_class: local_polling, local_push, cloud_polling, cloud_push, calculated
integration_type: most common values are hub (gateway/multiple devices), device (single device), service (cloud service); full set is device, entity (single entity), hardware, helper (logic-only helper), hub, service, system, virtual; defaults to hub when omitted
Required for custom/HACS distribution:
version: SemVer (required for custom integrations)
issue_tracker: URL for bug reports
The hard HACS requirements are domain, name, codeowners, documentation, issue_tracker, and version.
Optional:
config_flow: set true for new integrations (omit for YAML-only legacy)
dependencies: may be an empty array, but the key is not mandatory
requirements: may be an empty array, but the key is not mandatory
init.py Template (2025 Pattern)
"""The {Name} integration."""
from __future__ import annotations
import logging
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from .const import DOMAIN
from .coordinator import {Name}Coordinator
_LOGGER = logging.getLogger(__name__)
PLATFORMS: list[Platform] = [Platform.SENSOR]
# Type alias for config entry with typed runtime_data
type {Name}ConfigEntry = ConfigEntry[{Name}Coordinator]
async def async_setup_entry(hass: HomeAssistant, entry: {Name}ConfigEntry) -> bool:
"""Set up {Name} from a config entry."""
coordinator = {Name}Coordinator(hass, entry)
await coordinator.async_config_entry_first_refresh()
# Store coordinator in runtime_data (modern pattern, not hass.data)
entry.runtime_data = coordinator
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True
async def async_unload_entry(hass: HomeAssistant, entry: {Name}ConfigEntry) -> bool:
"""Unload a config entry."""
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
# Listeners registered via entry.async_on_unload(...) are cleaned automatically;
# any client/session must be closed explicitly here to avoid leaking connections.
await entry.runtime_data.client.async_close()
return unload_ok
const.py Template
"""Constants for the {Name} integration."""
from typing import Final
DOMAIN: Final = "{domain}"
DEFAULT_SCAN_INTERVAL: Final = 30
Python Version Requirements
- HA 2025.2+ ships on Python 3.13; develop and test against 3.13
- Use modern type syntax:
list[str] not List[str]
- Use
from __future__ import annotations in every file
- All I/O must be async
Critical Rules
- Config flow is mandatory — YAML-only configuration is not permitted
- Library separation — device communication in a separate PyPI package (required for core, recommended for custom)
- DataUpdateCoordinator — always use for polling integrations
- Unique IDs — every entity must have stable
unique_id
- Device info — group entities under devices using
DeviceInfo
- runtime_data — store coordinator in
entry.runtime_data, not hass.data
Additional Resources
- Config flow patterns:
ha-config-flow skill
- Coordinator implementation:
ha-coordinator skill
- Entity platforms:
ha-entity-platforms skill
- Testing:
ha-testing skill
1---2name: ha-integration-scaffold3description: Scaffold a new Home Assistant integration with correct file structure, manifest, and boilerplate. Use when creating a new custom component, custom integration, or when asked to scaffold, create, or start a Home Assistant integration.4---56# Home Assistant Integration Scaffolding78## Required File Structure910```text11custom_components/{domain}/12├── __init__.py # Entry point: async_setup_entry, async_unload_entry13├── manifest.json # Integration metadata (REQUIRED)14├── config_flow.py # UI-based configuration (REQUIRED)15├── const.py # Domain constant, platform list16├── coordinator.py # DataUpdateCoordinator subclass17├── entity.py # Base entity class (recommended)18├── strings.json # Config flow strings19├── services.yaml # Service action definitions (if registering services)20├── icons.json # Entity and service icons (optional)21├── translations/22│ └── en.json # English translations23└── [platform].py # One per entity platform (sensor.py, switch.py, etc.)24```2526**For HACS distribution, also include at repository root:**2728```text29/30├── custom_components/{domain}/ # Integration files31├── hacs.json # HACS metadata (REQUIRED for HACS)32├── README.md # Documentation33└── LICENSE # License file34```3536## manifest.json (2025 Requirements)3738```json39{40 "domain": "{domain_name}",41 "name": "{Human Readable Name}",42 "version": "1.0.0",43 "codeowners": ["@{github_username}"],44 "config_flow": true,45 "dependencies": [],46 "documentation": "https://github.com/{user}/{repo}",47 "integration_type": "hub",48 "iot_class": "local_polling",49 "issue_tracker": "https://github.com/{user}/{repo}/issues",50 "requirements": []51}52```5354**Always required:**5556- `domain`: lowercase, underscores only, matches folder name57- `name`: human-readable58- `codeowners`: GitHub usernames with `@` prefix59- `documentation`: URL to integration docs60- `iot_class`: `local_polling`, `local_push`, `cloud_polling`, `cloud_push`, `calculated`61- `integration_type`: most common values are `hub` (gateway/multiple devices), `device` (single device), `service` (cloud service); full set is `device`, `entity` (single entity), `hardware`, `helper` (logic-only helper), `hub`, `service`, `system`, `virtual`; defaults to `hub` when omitted6263**Required for custom/HACS distribution:**6465- `version`: SemVer (required for custom integrations)66- `issue_tracker`: URL for bug reports6768The hard HACS requirements are `domain`, `name`, `codeowners`, `documentation`, `issue_tracker`, and `version`.6970**Optional:**7172- `config_flow`: set `true` for new integrations (omit for YAML-only legacy)73- `dependencies`: may be an empty array, but the key is not mandatory74- `requirements`: may be an empty array, but the key is not mandatory7576## **init**.py Template (2025 Pattern)7778```python79"""The {Name} integration."""80from __future__ import annotations8182import logging8384from homeassistant.config_entries import ConfigEntry85from homeassistant.const import Platform86from homeassistant.core import HomeAssistant8788from .const import DOMAIN89from .coordinator import {Name}Coordinator9091_LOGGER = logging.getLogger(__name__)9293PLATFORMS: list[Platform] = [Platform.SENSOR]9495# Type alias for config entry with typed runtime_data96type {Name}ConfigEntry = ConfigEntry[{Name}Coordinator]979899async def async_setup_entry(hass: HomeAssistant, entry: {Name}ConfigEntry) -> bool:100 """Set up {Name} from a config entry."""101 coordinator = {Name}Coordinator(hass, entry)102 await coordinator.async_config_entry_first_refresh()103104 # Store coordinator in runtime_data (modern pattern, not hass.data)105 entry.runtime_data = coordinator106107 await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)108 return True109110111async def async_unload_entry(hass: HomeAssistant, entry: {Name}ConfigEntry) -> bool:112 """Unload a config entry."""113 if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):114 # Listeners registered via entry.async_on_unload(...) are cleaned automatically;115 # any client/session must be closed explicitly here to avoid leaking connections.116 await entry.runtime_data.client.async_close()117 return unload_ok118```119120## const.py Template121122```python123"""Constants for the {Name} integration."""124from typing import Final125126DOMAIN: Final = "{domain}"127DEFAULT_SCAN_INTERVAL: Final = 30128```129130## Python Version Requirements131132- HA 2025.2+ ships on **Python 3.13**; develop and test against 3.13133- Use modern type syntax: `list[str]` not `List[str]`134- Use `from __future__ import annotations` in every file135- All I/O must be async136137## Critical Rules1381391. **Config flow is mandatory** — YAML-only configuration is not permitted1402. **Library separation** — device communication in a separate PyPI package (required for core, recommended for custom)1413. **DataUpdateCoordinator** — always use for polling integrations1424. **Unique IDs** — every entity must have stable `unique_id`1435. **Device info** — group entities under devices using `DeviceInfo`1446. **runtime_data** — store coordinator in `entry.runtime_data`, not `hass.data`145146## Additional Resources147148- Config flow patterns: `ha-config-flow` skill149- Coordinator implementation: `ha-coordinator` skill150- Entity platforms: `ha-entity-platforms` skill151- Testing: `ha-testing` skill