Skip to main content

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:
1
Choose Your Domain
2
Pick a unique domain name for your integration:
3
{
  "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"]
}
4
Add Dependencies
5
If your integration depends on other integrations:
6
{
  "domain": "my_integration",
  "dependencies": ["http"],
  "after_dependencies": ["hassio"]
}
7
See the Integration Manifest guide for complete manifest options.

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:
__init__.py
"""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:
__init__.py
"""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.

Step 3: Create Platform Files

Each platform (light, sensor, etc.) lives in its own file.

Example: Light Platform

light.py
"""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()

Example: Sensor Platform

sensor.py
"""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:
const.py
"""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

1
Install in Development Mode
2
# Copy to custom_components for testing
mkdir -p config/custom_components/my_integration
cp -r homeassistant/components/my_integration/* config/custom_components/my_integration/
3
Add to configuration.yaml (if YAML-based)
4
my_integration:
  host: "192.168.1.100"
5
Restart Home Assistant
6
hass --script check_config
hass
7
Check Logs
8
Monitor logs for errors:
9
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

Build docs developers (and LLMs) love