Skip to main content

Changes to the condition and script APIs

· 2 min read

Summary

The condition and script APIs have been changed.

Conditions are now instances of condition classes, which are evaluated by calling the async_check method and discarded by calling the async_unload method. Also, conditions may optionally implement an _async_setup or _async_unload method. Note that users of conditions don't need to call the condition's async_setup method.

During a deprecation period, which ends with the release of Home Assistant Core 2027.1, it's possible to use the condition object as a callable.

Scripts also have an async_unload method which must be called when the script is no longer needed.

Impact on custom integrations

Custom integrations which create conditions or scripts

Custom integrations which create condition objects should evaluate them by calling the async_check method and call the async_unload method when the condition is no longer needed.

Example:

from homeassistant.helpers.condition import (
async_condition_from_config,
async_validate_condition_config,
)

# Validate condition config
validated_config = await async_validate_condition_config(hass, config)

# Create a condition
condition = await async_condition_from_config(hass, validated_config)

...

# Evaluate the condition
result = condition.async_check(...)
...

# Discard the condition
condition.async_unload()

Custom integrations which create scripts should call the async_unload method when the script is no longer needed.

Example:

from homeassistant.helpers.script import (
Script,
async_validate_actions_config,
)

# Validate script config
validated_config = await async_validate_actions_config(hass, config)

# Create a script
script = Script(hass, validated_config, ...)

...

# Execute the script
result = await script.async_run(...)
...

# Discard the script
await script.async_unload()

Custom integrations which provide a condition platform

Integrations which provide a condition platform don't need to change, but may implement _async_setup and _async_unload method if the platform needs to perform async initialization or do tear down.

Example:


from homeassistant.core import HomeAssistant
from homeassistant.helpers.condition import (
Condition,
ConditionCheckParams,
ConditionConfig,
)
from homeassistant.helpers.typing import ConfigType

class CustomCondition(Condition):
"""A custom condition."""

@classmethod
async def async_validate_config(
cls, hass: HomeAssistant, config: ConfigType
) -> ConfigType:
"""Validate config."""
...

def __init__(self, hass: HomeAssistant, config: ConditionConfig) -> None:
"""Initialize condition."""
super().__init__(hass, config)
...

async def _async_setup(self) -> None:
"""Set up the condition checker."""
...

def _async_unload(self) -> None:
"""Clean up any resources held by the checker."""
...

def _async_check(self, **kwargs: Unpack[ConditionCheckParams]) -> bool:
"""Check the condition."""
...

Format entity names in custom cards

· One min read

As of Home Assistant 2026.4, the hass object exposes a formatEntityName helper. It is the same function used by the built-in cards (tile card, entity rows, ...) to compute the display name of an entity from its registry context (entity, device, area, floor). Custom cards can use it to produce names that stay consistent with the rest of the dashboard.

Given a temperature sensor named Temperature on a device named Thermostat:

const stateObj = hass.states["sensor.living_room_thermostat_temperature"];

hass.formatEntityName(
stateObj,
[{ type: "device" }, { type: "entity" }],
{ separator: " · " }
); // "Thermostat · Temperature"

The frontend also ships an entity_name selector. If your card uses the built-in form editor, you can offer users the same name picker the built-in cards use — accepting either a free-form string or a composition of registry items.

Take a look at the updated data documentation for the full reference and more examples.

MQTT publish API changes

· One min read

In the future, the MQTT publish API will require explicit values for qos and retain. Passing None for either argument will no longer be supported. Custom integrations should update their code to accept the defaults, or pass valid typed arguments. The fallbacks of None to a valid value for qos and retain will stop working with HA Core 2027.6.

The new API signatures are:

def publish(
hass: HomeAssistant,
topic: str,
payload: PublishPayloadType,
qos: int = 0,
retain: bool = False,
encoding: str | None = DEFAULT_ENCODING,
) -> None:
"""Publish message to a MQTT topic."""
hass.create_task(async_publish(hass, topic, payload, qos, retain, encoding))

and

async def async_publish(
hass: HomeAssistant,
topic: str,
payload: PublishPayloadType,
qos: int = 0,
retain: bool = False,
encoding: str | None = DEFAULT_ENCODING,
) -> None:
"""Publish message to a MQTT topic."""

MQTT publish API supports message expiry interval

· One min read

The MQTT publish API now supports setting a message expiry interval. Previously, retained messages were stored by the broker until they were replaced or explicitly cleared. With a message_expiry_interval set (in seconds), a published message — including a retained one — will automatically expire after the specified interval. This option is only supported when using MQTT protocol version 5; it is ignored when using earlier protocol versions.

The new API signatures are:

def publish(
hass: HomeAssistant,
topic: str,
payload: PublishPayloadType,
qos: int = 0,
retain: bool = False,
encoding: str | None = DEFAULT_ENCODING,
*,
message_expiry_interval: int | None = None,
) -> None:
"""Publish message to a MQTT topic."""

and

async def async_publish(
hass: HomeAssistant,
topic: str,
payload: PublishPayloadType,
qos: int = 0,
retain: bool = False,
encoding: str | None = DEFAULT_ENCODING,
*,
message_expiry_interval: int | None = None,
) -> None:
"""Publish message to a MQTT topic."""

Deprecating config entry listener with reloading methods in config flow

· One min read

As of Home Assistant Core 2026.6, using a config entry listener together with any reloading methods in a config flow is deprecated and will result in an error from 2026.12.

Background

Using a config entry listener together with any reloading methods in a config flow can cause the integration to reload twice and/or create a race condition.

Possible solutions

  • Remove the config entry listener and rely only on the reloading methods in your config flow.
  • Use async_update_and_abort() instead of async_update_reload_and_abort().
  • Set reload_on_update=False when calling _abort_if_unique_id_configured().

More details can be found in the core PR.

Frontend component updates in 2026.5

· 4 min read

Component updates

ha-progress-bar

A new component replaces mwc-progress-bar in our codebase and is fully themeable. Check out this PR for a fully custom progress bar.

New component specific tokens:

--ha-progress-bar-indicator-color
--ha-progress-bar-indicator-background
--ha-progress-bar-track-color
--ha-progress-bar-track-height
--ha-progress-bar-border-radius
--ha-progress-bar-animation-duration
--ha-progress-bar-indicator-highlight-image
--ha-progress-bar-indicator-highlight-width
--ha-progress-bar-indicator-highlight-height

ha-switch

ha-switch was migrated to webawesome. It now has many CSS properties to make customization easier. We also removed some tokens from the old switch:

Removed tokens:

--switch-unchecked-button-color
--switch-unchecked-track-color
--switch-unchecked-color
--switch-checked-button-color
--switch-checked-track-color
--switch-checked-color

New component specific tokens:

--ha-switch-size
--ha-switch-thumb-size
--ha-switch-width
--ha-switch-background-color
--ha-switch-thumb-background-color
--ha-switch-background-color-hover
--ha-switch-thumb-background-color-hover
--ha-switch-checked-background-color
--ha-switch-checked-thumb-background-color
--ha-switch-checked-background-color-hover
--ha-switch-checked-thumb-background-color-hover
--ha-switch-border-color
--ha-switch-thumb-border-color
--ha-switch-thumb-border-color-hover
--ha-switch-checked-border-color
--ha-switch-checked-thumb-border-color
--ha-switch-checked-border-color-hover
--ha-switch-checked-thumb-border-color-hover
--ha-switch-thumb-box-shadow
--ha-switch-disabled-opacity
--ha-switch-required-marker
--ha-switch-required-marker-offset

ha-checkbox

ha-checkbox was also migrated to webawesome and got new CSS properties. You can no longer use MDC tokens for it, but we added a set of tokens to customize the new checkbox.

New component specific tokens:

--ha-checkbox-size
--ha-checkbox-border-color
--ha-checkbox-border-color-hover
--ha-checkbox-background-color
--ha-checkbox-background-color-hover
--ha-checkbox-checked-background-color
--ha-checkbox-checked-background-color-hover
--ha-checkbox-checked-icon-color
--ha-checkbox-checked-icon-scale
--ha-checkbox-border-radius
--ha-checkbox-border-width
--ha-checkbox-required-marker
--ha-checkbox-required-marker-offset

ha-textarea

ha-textarea was migrated to webawesome and got new CSS properties. You can no longer use MDC tokens for it, but we added a set of tokens to customize the new textarea.

The API changed slightly. To make your textarea grow in size, set the resize prop to auto.

ha-adaptive-popover

You might already know the (fairly new) ha-adaptive-dialog component. It combines a dialog and bottom sheet by showing a dialog on desktop and a bottom sheet on mobile. We added ha-adaptive-popover as the popover counterpart to the adaptive dialog. It shows a popover on desktop and a bottom sheet on mobile. It is based on ha-adaptive-dialog and is currently used in the tile card date picker feature.

Removed ha-fab

ha-fab was removed, we use just a normal ha-button now, since the position styling was always done from the parent component.

Style updates

Box shadow tokens

We added new global box shadow tokens: --ha-box-shadow-s, --ha-box-shadow-m, --ha-box-shadow-l

But we also removed the old box shadow tokens:

--ha-color-shadow-light
--ha-color-shadow-dark
--ha-shadow-offset-x-...
--ha-shadow-blur-...
--ha-shadow-spread-..

Surface colors

In the next release we plan to change the way we handle surface background colors. The tokens are introduced now but are just used for ha-tooltip.

New tokens:

--ha-color-surface-default
--ha-color-surface-low
--ha-color-surface-lower
--ha-color-surface-default-inverted
--ha-color-surface-low-inverted
--ha-color-surface-lower-inverted

Frontend context groups, new context decorators and deprecated contexts

· 2 min read

Last release we introduced the lazy context and we wanted to use hass context and lazy context more in favor of passing the hass object. We found that small tasks often required subscribing to many contexts, so we grouped Hass contexts into logical groups.

Context groups

Following context groups have been added:

  • registriesContext: contains all registries (device, entity, area, etc)
  • internationalizationContext: contains all internationalization related data (locale, localize, etc)
  • apiContext: contains all API related methods (callService, callWS, etc)
  • connectionContext: contains all connection related data (connection, connected, hassUrl, etc)
  • uiContext: contains all UI related data (themes, panels, dockedSidebar, etc)
  • configContext: contains all config related data (auth, config, user, etc)

Consume context entry decorators

In the last blogpost I showed how to use context with @transform. @transform is powerful, but sometimes you only need a single context entry and do not want to define a transform function. To support that, we added decorators that consume a single context entry directly. You pass an array that defines where the entity ID comes from (usually a config property), and the decorator consumes the correct context and maps the entity ID to the correct registry entry. It also watches the defined path and updates the entry when needed.

New decorators:

  • @consumeEntityState
  • @consumeEntityStates
  • @consumeEntityRegistryEntry

sample usage:

@state()
@consumeEntityRegistryEntry({ entityIdPath: ["_config", "entity"] })
_entity?: EntityRegistryDisplayEntry;

We may add more of these decorators in the future, e.g. for areas, devices, etc.

Deprecate contexts

These contexts are still available, but they can be removed in a future release. Use the new context groups instead.

  • connectionSingleContext
  • localizeContext
  • localeContext
  • configSingleContext
  • themesContext
  • selectedThemeContext
  • userContext
  • userDataContext
  • panelsContext
  • authContext

Serious about serial: migrating from pyserial to serialx

· 2 min read

Existing integrations and libraries communicating with serial ports should migrate from pyserial, pyserial-asyncio, and pyserial-asyncio-fast to serialx. This new library features native asyncio support on all platforms and will allow your integrations and libraries to take advantage of ESPHome serial proxies in Home Assistant, and includes critical fixes for asyncio event loop stability.

Background

pyserial has been the de facto serial library in Python for many years and has broad support for all popular platforms. Its API, however, predates asyncio in Python and is sync-only. The pyserial-asyncio package was eventually released to bridge pyserial with asyncio. Unfortunately, pyserial-asyncio development never reached 1.0 stability or cross-platform support, and neither pyserial nor pyserial-asyncio have had PyPI releases in almost five years. We forked pyserial-asyncio and released pyserial-asyncio-fast to fix outstanding issues affecting Core event loop stability.

We developed serialx from the ground up as a modern Python serial library with native sync and async APIs. It is import-compatible with the serial, serial_asyncio, and serial_asyncio_fast modules and allows existing packages to migrate with very minimal changes, if any.

Migration

The serialx documentation has an extensive migration guide that goes into more detail.

Most packages just need to replace pyserial, pyserial-asyncio, and pyserial-asyncio-fast with serialx in their setup.py or pyproject.toml file and update exception handling to replace broad catching of SerialException with more granular error handling (such as OSError and TimeoutError).

Packages directly constructing a sync serial.Serial() object should migrate to using the serialx.serial_for_url() helper to ensure automatic compatibility with all supported protocols.

Prompt for your agent

The migration itself is mechanical, paste the following instructions into your agent of choice:

Migrate my code from pyserial, pyserial-asyncio, and pyserial-asyncio-fast to serialx using https://raw.githubusercontent.com/puddly/serialx/refs/heads/dev/docs/how-to/pyserial-migration.md

New radio frequency entity platform for RF device integrations

· 3 min read

Home Assistant now has a radio_frequency entity platform that decouples RF transceiver hardware from the devices it controls. Instead of each device integration talking directly to specific RF hardware, transmitter integrations (like esphome) expose RadioFrequencyTransmitterEntity instances, and device integrations send commands through them via helper functions.

This mirrors the infrared entity platform and was approved in architecture discussion #1365.

Registering custom dashboard strategies

· 2 min read

As of Home Assistant 2026.5, you can now register custom dashboard strategies, just as you can with custom cards, making them easier to discover and add using the new dashboard dialog under the Community dashboards section.

Previously, you had to send users to create a blank dashboard, edit in YAML mode, and paste in your custom strategy. Now you can register a friendly name, description, and documentation.

To register your strategy, call window.customStrategies.push() with an object containing the following keys:

  • type: The strategy type without the custom: prefix, for example "my-demo".
  • strategyType: Set to "dashboard" to register a dashboard strategy.
  • name: The friendly name of the strategy.
  • description (optional): A short description of the strategy.
  • documentationURL (optional): A URL to the documentation for the strategy. This is not shown in the strategy UI yet but may in the future.

Example:

window.customStrategies = window.customStrategies || [];
window.customStrategies.push({
type: "my-demo",
strategyType: "dashboard",
name: "My demo dashboard",
description: "A starter dashboard generated from JavaScript.",
documentationURL: "https://example.com/my-demo-dashboard",
});

This metadata is separate from the custom element itself. Your strategy still needs to be registered with a tag like ll-strategy-dashboard-my-demo, and users still need the resource loaded before Home Assistant can discover it. You can use HACS for this as other resources can be added, like custom cards.

Take a look at the updated custom strategies documentation with example code and further details.