Skip to content

Configuration

AppDaemon splits configuration across two YAML files: appdaemon.yaml for global settings and apps.yaml for per-app arguments. Hassette uses a single hassette.toml for everything, and replaces raw argument dictionaries with typed AppConfig models — AppConfig is a class you define once per app, declaring each setting's name and type; Hassette fills it from hassette.toml and hands it to your app as self.app_config.

Global Configuration

appdaemon:
  time_zone: America/Chicago
  latitude: 51.725
  longitude: 14.3434
  elevation: 0
  use_dictionary_unpacking: true
  plugins:
    HASS:
      type: hass
      ha_url: http://192.168.1.179:8123
      token: !env_var HOME_ASSISTANT_TOKEN
[hassette]
base_url = "http://192.168.1.179:8123"
# Token read from HASSETTE__TOKEN env var or .env file

[hassette.apps.my_app]
filename = "my_app.py"
class_name = "MyApp"

# [[double brackets]] = TOML array-of-tables; supports running the same app with multiple configs
[[hassette.apps.my_app.config]]
entity = "light.kitchen"
brightness = 200

The Home Assistant token is read from the HASSETTE__TOKEN environment variable or a .env file. It does not go in hassette.toml.

Per-App Configuration

This is the bigger change. In AppDaemon, you declare app arguments in apps.yaml and read them through a nested dictionary. In Hassette, arguments go in hassette.toml and you define an AppConfig subclass to describe them.

my_app:
  module: my_app
  class: MyApp
  args:
    entity: light.kitchen
    brightness: 200

Arguments are accessible via self.args["args"]["key"], a nested dictionary with no type information:

from appdaemon.plugins.hass import Hass


class MyApp(Hass):
    def initialize(self):
        self.log(f"{self.args=}")
        entity = self.args["args"]["entity"]
        brightness = self.args["args"]["brightness"]
        self.log(f"My configured entity is {entity!r} (type {type(entity)})")
        self.log(f"My configured brightness is {brightness!r} (type {type(brightness)})")

        # 2025-10-13 18:59:04.820599 INFO my_app: self.args={'name': 'my_app', 'config_path': PosixPath('./apps.yaml'), 'module': 'my_app', 'class': 'MyApp', 'args': {'entity': 'light.kitchen', 'brightness': 200}}
        # 2025-10-13 18:40:23.676650 INFO my_app: My configured entity is 'light.kitchen' (type <class 'str'>)
        # 2025-10-13 18:40:23.677422 INFO my_app: My configured brightness is 200 (type <class 'int'>)
[hassette]
base_url = "http://127.0.0.1:8123"

[hassette.apps.my_app]
filename = "my_app.py"
class_name = "MyApp"

[[hassette.apps.my_app.config]]
entity = "light.kitchen"
brightness = 200

You define a subclass of AppConfig to declare each parameter with a type and optional default. Access configuration through self.app_config:

from pydantic import Field

from hassette import App, AppConfig


class MyAppConfig(AppConfig):
    entity: str = Field(..., description="The entity to monitor")
    brightness: int = Field(100, ge=0, le=255, description="Brightness level (0-255)")


class MyApp(App[MyAppConfig]):
    async def on_initialize(self):
        self.logger.info("app_manifest=%r", self.app_manifest)
        self.logger.info("app_config=%r", self.app_config)
        entity = self.app_config.entity
        self.logger.info("My configured entity is %r (type %s)", entity, type(entity))
        brightness = self.app_config.brightness
        self.logger.info("My configured brightness is %r (type %s)", brightness, type(brightness))

        # 2025-10-13 18:57:45.495 INFO hassette.MyApp.0.on_initialize:13 - self.app_manifest=<AppManifest MyApp (MyApp) - enabled=True file=my_app.py>
        # 2025-10-13 18:57:45.495 INFO hassette.MyApp.0.on_initialize:14 - self.app_config=MyAppConfig(instance_name='MyApp.0', log_level='INFO', entity='light.kitchen', brightness=200)
        # 2025-10-13 18:57:45.495 INFO hassette.MyApp.0.on_initialize:17 - My configured entity is 'light.kitchen' (type <class 'str'>)
        # 2025-10-13 18:57:45.495 INFO hassette.MyApp.0.on_initialize:19 - My configured brightness is 200 (type <class 'int'>)

Missing required fields raise a validation error at startup, before any handler runs — the error names the missing key, so a failed config migration surfaces immediately rather than as a KeyError mid-automation. self.app_config.entity carries a type your IDE can check.

[[double brackets]]: TOML array-of-tables

[[hassette.apps.my_app.config]] is a TOML array-of-tables. You can repeat the block to run the same app class with multiple independent configurations. Use [...] for a single instance; use [[...]] when you want a list.

Multi-Instance Apps

To run the same class in multiple rooms, add another [[hassette.apps.my_app.config]] block:

[hassette.apps.motion_lights]
filename = "motion_lights.py"
class_name = "MotionLights"

[[hassette.apps.motion_lights.config]]
motion_sensor = "binary_sensor.living_room_motion"
light = "light.living_room"
off_delay = 300

[[hassette.apps.motion_lights.config]]
motion_sensor = "binary_sensor.bedroom_motion"
light = "light.bedroom"
off_delay = 120

Each block becomes a separate app instance. Both run the same MotionLights class with different config values, and each instance's self.app_config holds the values from its own block — the Python class needs no changes. See App Configuration for the full reference.

See Also