Creating Components
This guide walks you through creating a complete Home Assistant integration from scratch. We’ll build a simple integration that demonstrates all the key concepts.
Integration Structure
Every integration follows a standard directory structure:
homeassistant/components/your_integration/
├── __init__.py # Main integration file
├── manifest.json # Integration metadata
├── config_flow.py # UI configuration (optional)
├── const.py # Constants
├── light.py # Light platform (example)
├── sensor.py # Sensor platform (example)
└── strings.json # Translations
Step 1: Create the Manifest
The manifest.json file defines your integration’s metadata:
Pick a unique domain name for your integration:
{
"domain" : "my_integration" ,
"name" : "My Integration" ,
"codeowners" : [ "@yourusername" ],
"config_flow" : true ,
"documentation" : "https://www.home-assistant.io/integrations/my_integration" ,
"integration_type" : "hub" ,
"iot_class" : "local_polling" ,
"requirements" : [ "my-library==1.0.0" ]
}
If your integration depends on other integrations:
{
"domain" : "my_integration" ,
"dependencies" : [ "http" ],
"after_dependencies" : [ "hassio" ]
}
Step 2: Implement the Main Component
The __init__.py file is the entry point for your integration.
YAML-Based Setup (Legacy)
For simple integrations or those requiring YAML configuration:
"""The My Integration integration."""
from __future__ import annotations
import logging
import voluptuous as vol
from homeassistant.const import CONF_HOST
from homeassistant.core import HomeAssistant
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.typing import ConfigType
_LOGGER = logging.getLogger( __name__ )
DOMAIN = "my_integration"
# Validation schema for configuration.yaml
CONFIG_SCHEMA = vol.Schema(
{
DOMAIN : vol.Schema(
{
vol.Required( CONF_HOST ): cv.string,
}
)
},
extra = vol. ALLOW_EXTRA ,
)
async def async_setup ( hass : HomeAssistant, config : ConfigType) -> bool :
"""Set up the My Integration component."""
conf = config.get( DOMAIN )
if conf is None :
return True
host = conf[ CONF_HOST ]
_LOGGER .info( "Setting up My Integration with host: %s " , host)
# Store configuration data
hass.data[ DOMAIN ] = {
"host" : host,
}
# Load platforms
await hass.helpers.discovery.async_load_platform(
"sensor" , DOMAIN , {}, config
)
return True
Config Entry-Based Setup (Recommended)
For modern integrations with UI configuration:
"""The My Integration integration."""
from __future__ import annotations
import logging
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_HOST , Platform
from homeassistant.core import HomeAssistant
_LOGGER = logging.getLogger( __name__ )
DOMAIN = "my_integration"
# List of platforms your integration supports
PLATFORMS = [Platform. LIGHT , Platform. SENSOR ]
async def async_setup_entry ( hass : HomeAssistant, entry : ConfigEntry) -> bool :
"""Set up My Integration from a config entry."""
host = entry.data[ CONF_HOST ]
# Initialize your API client
# api = MyIntegrationAPI(host)
# await api.connect()
# Store the API client for platforms to use
hass.data.setdefault( DOMAIN , {})
hass.data[ DOMAIN ][entry.entry_id] = {
"host" : host,
# "api": api,
}
# Forward entry setup to platforms
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS )
return True
async def async_unload_entry ( hass : HomeAssistant, entry : ConfigEntry) -> bool :
"""Unload a config entry."""
# Unload platforms
unload_ok = await hass.config_entries.async_unload_platforms(
entry, PLATFORMS
)
if unload_ok:
# Clean up stored data
hass.data[ DOMAIN ].pop(entry.entry_id)
return unload_ok
The async_setup_entry method is called when a user configures your integration through the UI. See Config Flow for implementation details.
Each platform (light, sensor, etc.) lives in its own file.
"""Light platform for My Integration."""
from __future__ import annotations
from typing import Any
from homeassistant.components.light import (
ATTR_BRIGHTNESS ,
ColorMode,
LightEntity,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import DOMAIN
async def async_setup_entry (
hass : HomeAssistant,
entry : ConfigEntry,
async_add_entities : AddConfigEntryEntitiesCallback,
) -> None :
"""Set up light platform."""
# Get data from the main component
data = hass.data[ DOMAIN ][entry.entry_id]
# Create light entities
async_add_entities([
MyLight( "Living Room Light" , "light_1" ),
MyLight( "Bedroom Light" , "light_2" ),
])
class MyLight ( LightEntity ):
"""Representation of a My Integration Light."""
_attr_has_entity_name = True
_attr_color_mode = ColorMode. BRIGHTNESS
_attr_supported_color_modes = {ColorMode. BRIGHTNESS }
def __init__ ( self , name : str , unique_id : str ) -> None :
"""Initialize the light."""
self ._attr_name = name
self ._attr_unique_id = unique_id
self ._attr_is_on = False
self ._attr_brightness = 255
async def async_turn_on ( self , ** kwargs : Any) -> None :
"""Turn the light on."""
if ATTR_BRIGHTNESS in kwargs:
self ._attr_brightness = kwargs[ ATTR_BRIGHTNESS ]
self ._attr_is_on = True
# Call your API here to turn on the light
# await self.api.turn_on(self._attr_brightness)
self .async_write_ha_state()
async def async_turn_off ( self , ** kwargs : Any) -> None :
"""Turn the light off."""
self ._attr_is_on = False
# Call your API here to turn off the light
# await self.api.turn_off()
self .async_write_ha_state()
"""Sensor platform for My Integration."""
from __future__ import annotations
from homeassistant.components.sensor import (
SensorDeviceClass,
SensorEntity,
SensorStateClass,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import UnitOfTemperature
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import DOMAIN
async def async_setup_entry (
hass : HomeAssistant,
entry : ConfigEntry,
async_add_entities : AddConfigEntryEntitiesCallback,
) -> None :
"""Set up sensor platform."""
data = hass.data[ DOMAIN ][entry.entry_id]
async_add_entities([
MyTemperatureSensor( "Living Room" , "temp_1" ),
])
class MyTemperatureSensor ( SensorEntity ):
"""Representation of a temperature sensor."""
_attr_has_entity_name = True
_attr_device_class = SensorDeviceClass. TEMPERATURE
_attr_state_class = SensorStateClass. MEASUREMENT
_attr_native_unit_of_measurement = UnitOfTemperature. CELSIUS
def __init__ ( self , name : str , unique_id : str ) -> None :
"""Initialize the sensor."""
self ._attr_name = name
self ._attr_unique_id = unique_id
self ._attr_native_value = None
async def async_update ( self ) -> None :
"""Fetch new state data for the sensor."""
# Fetch temperature from your API
# self._attr_native_value = await self.api.get_temperature()
self ._attr_native_value = 22.5 # Example value
See Entity Platforms for detailed information on implementing different entity types.
Step 4: Add Constants
Create a const.py file for constants:
"""Constants for My Integration."""
DOMAIN = "my_integration"
# Configuration
CONF_API_KEY = "api_key"
DEFAULT_PORT = 8080
# Defaults
DEFAULT_SCAN_INTERVAL = 60
Step 5: Testing Your Integration
Install in Development Mode
# Copy to custom_components for testing
mkdir -p config/custom_components/my_integration
cp -r homeassistant/components/my_integration/ * config/custom_components/my_integration/
Add to configuration.yaml (if YAML-based)
my_integration :
host : "192.168.1.100"
hass --script check_config
hass
tail -f home-assistant.log | grep my_integration
Best Practices
Never block the event loop : Always use async methods and await I/O operations.
Use Async/Await
# Good
async def async_turn_on ( self ):
await self .api.turn_on()
# Bad - blocks event loop
def turn_on ( self ):
self .api.turn_on() # Blocking call
Handle Errors Gracefully
async def async_update ( self ):
"""Update the sensor."""
try :
self ._attr_native_value = await self .api.get_value()
except ConnectionError :
_LOGGER .error( "Failed to connect to device" )
self ._attr_available = False
except Exception as err:
_LOGGER .exception( "Unexpected error: %s " , err)
Use Type Hints
from typing import Any
async def async_turn_on ( self , ** kwargs : Any) -> None :
"""Turn on the device."""
pass
Follow Naming Conventions
Integration domain: lowercase with underscores (e.g., my_integration)
Class names: PascalCase (e.g., MyLight)
Functions: snake_case (e.g., async_setup_entry)
Private members: prefix with underscore (e.g., _attr_name)
Real-World Example: Demo Integration
The Demo integration is a great reference:
# From homeassistant/components/demo/__init__.py
async def async_setup_entry (
hass : HomeAssistant, entry : ConfigEntry
) -> bool :
"""Set up a config entry."""
await hass.config_entries.async_forward_entry_setups(
entry, COMPONENTS_WITH_CONFIG_ENTRY_DEMO_PLATFORM
)
return True
Location: homeassistant/components/demo/__init__.py:163
Next Steps
Integration Manifest Learn about all manifest.json options
Config Flow Add UI-based configuration to your integration
Entity Platforms Deep dive into entity platform implementation