What Are Event Types in Home Assistant? Architecture and Usage Guide

Event types are string identifiers that categorize every event flowing through Home Assistant's internal event bus, defined as typed constants in homeassistant/const.py and used by the core EventBus class to fire events and register listeners.

Home Assistant's event-driven architecture relies on a robust publish/subscribe system to coordinate state changes, service calls, and lifecycle hooks across core components and integrations. Understanding how event types function is essential for developing custom automations or custom components. This guide examines the implementation details from the home-assistant/core repository, covering the generic EventType class, the EventBus dispatch mechanism, and practical usage patterns for firing and listening to events.

The EventType Class: Generic Type Safety for Event Strings

At the foundation of Home Assistant's event system lies the EventType class, defined in homeassistant/util/event_type.py. This is not a simple string constant but a generic subclass of str that carries compile-time type information about the event's data payload.


# homeassistant/util/event_type.py

class EventType[_DataT: Mapping[str, Any] = Mapping[str, Any]](str):
    """Custom type for Event.event_type.

    At runtime this is a generic subclass of str.
    """

The generic parameter _DataT describes the shape of the dictionary passed as event_data. While this generic typing is used only by static type checkers like MyPy (at runtime the object behaves exactly like a string), it enables IDE autocomplete and type checking for event handlers. By default, _DataT accepts any Mapping[str, Any], but specific event types constrain this to precise TypedDict structures.

Built-in Event Type Constants in homeassistant/const.py

Home Assistant pre-declares all core event identifiers in homeassistant/const.py. These constants serve as the contract between the core event bus and integration code, ensuring consistent naming across the codebase.

The repository uses two patterns for these constants:

Typed constants using EventType with specific payload shapes:


# homeassistant/const.py

EVENT_STATE_CHANGED: EventType[EventStateChangedData] = EventType("state_changed")
EVENT_HOMEASSISTANT_START: EventType[NoEventData] = EventType("homeassistant_start")

Untyped string constants for events without standardized payloads:


# homeassistant/const.py

EVENT_CALL_SERVICE: Final = "call_service"

The typed constants enable static analysis. For example, when listening to EVENT_STATE_CHANGED, type checkers know the event.data dictionary contains old_state and new_state keys conforming to the EventStateChangedData interface defined in homeassistant/helpers/event.py.

How the EventBus Uses Event Types for Dispatch

The EventBus class in homeassistant/core.py (around line 1422) manages the routing of events from publishers to subscribers. It validates event type strings, creates Event objects, and maintains the listener registry.

Firing Events with Type Validation

When firing an event, the bus validates the event type string length before processing:


# homeassistant/core.py (excerpt)

def async_fire(self, event_type, event_data=None, ...):
    """Fire an event. Must be called from the event loop."""
    _verify_event_type_length_or_raise(event_type)          # length guard

    return self.async_fire_internal(event_type, event_data, ...)

The _verify_event_type_length_or_raise helper ensures event type identifiers do not exceed internal limits. The async_fire method then creates an Event object containing the event_type, data, origin (whether the event came from local code or a remote source), and timestamp.

Registering Listeners by Event Type

Components subscribe to specific event types using async_listen, which supports optional filtering and execution control:


# homeassistant/core.py (excerpt)

def async_listen(self, event_type, listener, event_filter=None, ...):
    """Register a listener for a specific event type (or MATCH_ALL)."""
    filterable_job = (HassJob(listener, f"listen {event_type}"), event_filter)
    self._listeners[event_type].append(filterable_job)

Key features of the listener registration:

  • Specific or global listening: Pass an EventType constant to listen for specific events, or use MATCH_ALL to receive every event on the bus.
  • Event filtering: The optional event_filter parameter accepts a callable that inspects the Event object; the listener only triggers if the filter returns True.
  • Immediate execution: The run_immediately parameter controls whether the listener runs synchronously during event firing or is scheduled as a job.

Practical Usage: Firing and Listening for Events

Firing a Built-in Event

Integrations signal system state changes by firing pre-defined event types:

from homeassistant.const import EVENT_HOMEASSISTANT_START

# Inside an async function with access to the hass instance

hass.bus.async_fire(EVENT_HOMEASSISTANT_START)

Because EVENT_HOMEASSISTANT_START is typed as EventType[NoEventData], static analysis confirms no payload should be passed.

Listening for Typed Events with Callbacks

To react to state changes, register a listener with the EVENT_STATE_CHANGED constant:

from homeassistant.const import EVENT_STATE_CHANGED
from homeassistant.core import HomeAssistant, Event

async def async_state_changed(event: Event) -> None:
    """Callback invoked for every state change."""
    data = event.data          # Typed as EventStateChangedData

    old_state = data["old_state"]
    new_state = data["new_state"]
    # React to the state transition...

hass.bus.async_listen(EVENT_STATE_CHANGED, async_state_changed)

The Event class stores the event_type as a string attribute, while the data attribute contains the payload dictionary. Type inference ensures data is treated as EventStateChangedData when the typed constant is used.

Creating Custom Event Types

Integrations can define custom event types with typed payloads for internal communication:

from homeassistant.core import HomeAssistant, Event
from homeassistant.util.event_type import EventType
from typing import TypedDict

class MyData(TypedDict):
    value: int
    source: str

MY_EVENT = EventType[MyData]("my_custom_event")

async def handle_my(event: Event[MyData]) -> None:
    print(event.data["value"], event.data["source"])

hass.bus.async_listen(MY_EVENT, handle_my)
hass.bus.async_fire(MY_EVENT, {"value": 42, "source": "sensor"})

This pattern provides the same type safety as core events, allowing IDEs to validate that event.data contains the expected value and source keys.

Summary

  • Event types are specialized strings (instances of EventType) that categorize events in Home Assistant's publish/subscribe system.
  • The EventType generic class in homeassistant/util/event_type.py enables static type checking of event payloads without runtime overhead.
  • Built-in constants like EVENT_STATE_CHANGED and EVENT_HOMEASSISTANT_START are defined in homeassistant/const.py with specific payload types.
  • The EventBus in homeassistant/core.py validates event type lengths, creates Event objects, and dispatches them to registered listeners via async_fire and async_listen.
  • Listeners can filter events by type, apply custom event filters, or use MATCH_ALL to receive every event on the bus.

Frequently Asked Questions

What is the difference between EventType and a regular string constant?

EventType is a generic subclass of str defined in homeassistant/util/event_type.py that carries type information about the event's payload structure. While it behaves exactly like a string at runtime, it allows MyPy and IDEs to validate that event listeners handle the correct data shape. Regular string constants (like EVENT_CALL_SERVICE) lack this compile-time type safety.

How do I listen for all events regardless of type?

Pass the special constant MATCH_ALL (defined in homeassistant/core.py) as the event type when calling hass.bus.async_listen(). This registers the listener to receive every event fired on the bus. Be cautious with this approach, as it executes your callback for every state change, service call, and system event, which can impact performance.

Why does Home Assistant validate event type length?

The _verify_event_type_length_or_raise function in homeassistant/core.py enforces a maximum length on event type strings to prevent memory issues and ensure consistent behavior in the internal listener registry dictionary. This validation occurs inside async_fire before the event object is created.

Can I use custom event types in my custom integration?

Yes. Create an EventType instance with a TypedDict defining your payload structure, as shown in the custom event example. Fire it using hass.bus.async_fire() and listen for it using hass.bus.async_listen(). Using EventType rather than plain strings ensures your integration benefits from static type checking and clearly documents the expected event data schema.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →