Howtos for The Rest of Us

Skills

Home Assistant Integration Development

Structure

custom_components/my_integration/
├── manifest.json          # Version, domain, requirements, codeowners
├── __init__.py            # Setup platform + services
├── config_flow.py         # UI configuration (optional config flow)
├── const.py               # Domain constants, defaults
├── sensor.py              # Sensor platform
├── binary_sensor.py       # Binary sensor platform
├── switch.py              # Switch platform
├── number.py              # Number platform
├── select.py              # Select platform
├── diagnostics.py         # Diagnostics data dump
├── strings.json           # Localized strings for config flow
└── translations/          # Per-locale translations (en.json, etc.)

manifest.json

{
  "domain": "my_integration",
  "name": "My Integration",
  "version": "1.0.0",
  "requirements": ["httpx>=0.27"],
  "dependencies": [],
  "codeowners": ["@your-github-handle"],
  "config_flow": true,
  "iot_class": "local_polling",
  "documentation": "https://github.com/...",
  "issue_tracker": "https://github.com/...",
  "integration_type": "hub"
}

iot_class options: assumed_state, cloud_polling, cloud_push, local_polling, local_push (most common: local_polling for REST APIs)

Config Flow

import voluptuous as vol
DATA_SCHEMA = vol.Schema({
    vol.Required("host"): str,
    vol.Optional("port", default=8080): vol.Coerce(int),
    vol.Required("api_key"): str,
})

Coordinator Pattern

For polling integrations, use DataUpdateCoordinator:

class MyCoordinator(DataUpdateCoordinator):
    def __init__(self, hass, api):
        super().__init__(
            hass,
            logger,
            name="My Sensor",
            update_interval=timedelta(seconds=30),
        )

    async def _async_update_data(self):
        return await self.api.fetch_data()

Entity Best Practices

from homeassistant.components.sensor import SensorEntityDescription

SENSORS = [
    SensorEntityDescription(
        key="temperature",
        name="Temperature",
        native_unit_of_measurement=UnitOfTemperature.CELSIUS,
        device_class=SensorDeviceClass.TEMPERATURE,
        state_class=SensorStateClass.MEASUREMENT,
    ),
]

Services

Register services in async_setup_entry:

async def async_setup_entry(hass, entry):
    async def handle_sync(call):
        await hass.data[DOMAIN][entry.entry_id].sync()

    hass.services.async_register(DOMAIN, "sync", handle_sync)
    return True

Errors

Testing

HACS

{
  "name": "My Integration",
  "content_in_root": false,
  "render_readme": true,
  "domains": ["my_integration"],
  "iot_class": "local_polling"
}