home-assistant-best-practices

v2026.09.24

Best practices for HA automations, helpers, scripts, and dashboards. TRIGGER THIS SKILL WHEN: - Creating or editing automations, scripts, scenes, dashboards, blueprints - Choosing template sensors, helpers, or Jinja macros - Restructuring triggers, conditions, or modes; button, remote, or event-entity automations - Renaming entities or migrating device_id to entity_id - Looking up card types or domain docs; writing AppDaemon apps - Deleting or restoring a backup, or upgrading Core or the OS SYMPTOMS: - Jinja2 templates where native options exist - device_id used instead of entity_id - Entity IDs changed without checking consumers - Wrong automation mode chosen - Raw sensor or hard-coded value used where a helper belongs - Direct .storage edits, or generated YAML snippets - User told to edit configuration.yaml for UI integrations - Hardcoded Blueprint entities or skipped selectors - Existing state changed with no recovery path - Jinja copy-pasted between templates

GitHub
Install command
npx skhub add homeassistant-ai/home-assistant-best-practices
Markdown
SKILL.md

Home Assistant Best Practices

Core principle: Use native Home Assistant constructs wherever possible. Templates bypass validation, fail silently at runtime, and make debugging opaque.

Decision Workflow

Follow this sequence when creating any automation:

0. Gate: modifying existing config?

If your change affects entity IDs, display names, or cross-component references — renaming entities or devices, replacing template sensors with helpers, converting device triggers, or restructuring automations — read safe-refactoring first. That reference covers impact analysis, device-sibling discovery, display-name overrides, and post-change verification. Complete its workflow before proceeding.

Steps 1-5 below apply to new config or pattern evaluation.

1. Check for a purpose-specific, then generic native, trigger/condition

Since 2026.7 the default building blocks are purpose-specific triggers/conditions — <domain>.<name> keys (motion detected, battery low, door opened) with area/floor/label targets. Check for one that matches the intent first, then a generic native trigger/condition, and only then a template. See automation-patterns #purpose-specific-triggers--conditions-default-since-20267.

Common substitutions:

  • List of individual sensor entities in a trigger → one purpose-specific trigger with an area/floor/label target:
  • {{ states('x') | float > 25 }} → numeric_state condition with above: 25
  • {{ is_state('x', 'on') and is_state('y', 'on') }} → condition: and with state conditions
  • {{ now().hour >= 9 }} → condition: time with after: "09:00:00"
  • wait_template: "{{ is_state(...) }}" → wait_for_trigger with state trigger (caveat: different behavior when state is already true — see safe-refactoring #trigger-restructuring)

2. Check for built-in helper or Template Helper

Before creating a template sensor, check helper-selection.

Common substitutions:

  • Sum/average multiple sensors → min_max integration
  • Binary any-on/all-on logic → group helper
  • Rate of change → derivative integration
  • Cross threshold detection → threshold integration
  • Consumption tracking → utility_meter helper

If no built-in helper fits, use a Template Helper — not YAML. Create it via the HA config flow (programmatically or in the UI: Settings → Devices & Services → Helpers → Create Helper → Template). A flow-created helper is UI-editable; a template: YAML entry needs a template.reload and is not.

Write template: YAML when the user asks for it, when neither path is available, or when the config needs a key the flow has no field for — trigger-based templates and attributes: are the common ones. Then use managed YAML editing (yaml-only-integrations), not a hand-edit.

3. Select correct automation mode

Default single mode is often wrong. See automation-patterns #automation-modes.

ScenarioMode
Motion light with timeoutrestart
Sequential processing (door locks)queued
Independent per-entity actionsparallel
One-shot notificationssingle

4. Use entity_id over device_id

device_id breaks when devices are re-added. See device-control.

Exception: Zigbee2MQTT autodiscovered device triggers are acceptable.

5. For buttons and remotes

  • Any integration exposing an event.* entity: Use event.received targeting that entity — a normal entity, so it can be renamed and survives a re-add when the integration keeps a stable unique ID
  • ZHA: No event entities — use an event trigger with device_ieee (persistent)
  • Z2M: Event entities are experimental and off by default — use a device trigger (autodiscovered) or mqtt trigger

See device-control #buttonremote-patterns.


Critical Anti-Patterns

Anti-patternUse insteadWhyReference
condition: template with float > 25condition: numeric_stateValidated at load, not runtimeautomation-patterns #native-conditions
wait_template: "{{ is_state(...) }}"wait_for_trigger with state triggerEvent-driven, not polling; waits for change (see safe-refactoring #trigger-restructuring for semantic differences)automation-patterns #wait-actions
device_id in triggersentity_id (or device_ieee for ZHA)device_id breaks on re-adddevice-control #entity-id-vs-device-id
numeric_state trigger driving a costly action, unguardedCondition rejecting unavailable/unknown in trigger.from_stateA restart or blip re-arms the trigger, so an unchanged value fires with no crossing (the guard also drops real crossings)automation-patterns #unavailable-arms-a-numeric-state-trigger
mode: single for motion lightsmode: restartRe-triggers must reset the timerautomation-patterns #automation-modes
enabled: false as a top-level key in automations.yamlautomation.turn_off (temporary) or entity registry disable (permanent)Not a valid top-level key — rejected during schema validation; automation loads as unavailableautomation-patterns #disabling-automations
Template sensor for sum/meanmin_max helperDeclarative, handles unavailable stateshelper-selection #numeric-aggregation
Template binary sensor with thresholdthreshold helperBuilt-in hysteresis supporthelper-selection #threshold
Renaming entity IDs without impact analysisFollow safe-refactoring workflowRenames break dashboards, scripts, scenes, Config-Entry data, and storage dashboards silentlysafe-refactoring #entity-renames
Renaming members of Config-Entry-based groups (UI groups) without updating membershipUpdate group membership via Options Flow after the registry renameThe entity registry rename does not update options.entities in the Config Entry — group silently breakssafe-refactoring #config-entry-groups
Renaming entities used by Config-Entry integrations (Better/Generic Thermostat, Min/Max, Threshold) without patching Config-Entry dataScan and patch core.config_entries data+options fieldsThese integrations store entity_ids in Config Entry — not updated by entity registry renamessafe-refactoring #config-entry-data--blind-spots-for-entity-registry-renames
template: sensor/binary sensor in YAMLTemplate Helper via the config flowA flow helper reloads in place and stays UI-editable; a template: entry needs a config reload and does not. Exceptions are real — trigger-based templates and attributes: have no flow fieldhelper-selection #template-helpers
Editing .storage/ files or other HA internal state directlyUse the HA REST/WebSocket API to manage state and config entries.storage/ files are HA's internal state database; direct edits bypass validation, risk corruption, and can be silently overwritten by HA—
Writing raw YAML to configuration.yaml by hand for YAML-only integrationsUse managed YAML config editing with backup and validationUnmanaged writes risk syntax errors, have no backup, and skip check_config — managed editing provides all threeyaml-only-integrations
Generating YAML snippets for automations/scripts/scenesUse the HA config API to create automations/scripts programmaticallyAPI calls validate config, avoid syntax errors, and don't require manual file edits or restartsautomation-patterns, examples.yaml
Telling user to edit configuration.yaml for integrationsDirect user to Settings > Devices & Services in the HA UIMost integrations are UI-configured; YAML integration config is rare and integration-specific—
Referring to HA "add-ons"Use the term "Apps"HA renamed add-ons to Apps in 2026.2 — "Apps are standalone applications that run alongside Home Assistant"—
vacuum.send_command with vendor room IDsvacuum.clean_area with HA area_id (if segments are mapped)Uses native HA areas, works across integrations — but requires segment-to-area mapping in entity settings firstdevice-control #vacuum-control
Using color_temp (mireds) in light actionsUse color_temp_kelvinThe color_temp parameter was removed in 2026.3; only Kelvin is supporteddevice-control #lights
Person/Device Tracker entered_home/left_home device triggers or is_home/is_not_home conditionsstate trigger to: home / to: not_home, or state conditionThese were removed in 2026.5 — state triggers and conditions are the correct replacementsautomation-patterns #presence-and-person-triggers-and-conditions-removed-in-20265
Entity list in a trigger where an area/floor/label target fitsPurpose-specific trigger with target: {area_id: ...}Automation follows area membership as devices change — no stale entity listsautomation-patterns #purpose-specific-triggers--conditions-default-since-20267
Old purpose-specific keys (battery.low, vacuum.docked, timer.time_remaining, ...) or trigger behavior: any/lastRenamed 2026.7 keys (battery.became_low, ...) and behavior: each/allOld keys no longer load; old behavior values raise a repair issue and face removalautomation-patterns #purpose-specific-triggers--conditions-default-since-20267
AppDaemon: callbacks in __init__, uncancelled run_in timers, state in instance variables, hardcoded entity IDsRegister in initialize(), cancel before rescheduling, persist via input_* helpers, pass IDs through self.argsEach fails silently, resets on reload, or blocks reuseappdaemon #appdaemon-specific-anti-patterns
Blueprints: hardcoded entities, free text where a selector belongs, !input inside a template, missing source_urlTyped !input selectors; bind an input to variables: before templating it; always set source_urlHardcoding defeats reuse, text lets typos through, and !input is a YAML tag rather than a template valueblueprint-guide #common-pitfalls
Backups: full restore to undo one object edit, no backup before an irreversible operation (registry deletion, integration removal, Core/OS upgrade), calling an action "reversible" without naming its inverseRoll the single object back; take the backup before; name the exact inverse or treat it as irreversibleA full restore reverts every unrelated change since and restarts HA; a backup taken afterward captures the damagebackups #when-a-full-backup-earns-its-cost
Restoring a backup, deleting a backup, or upgrading Core or the OS without explicit user confirmationAsk, name the concrete effect, and wait for an answer — every time, backup or notA full restore discards everything since the archive for all restored parts and restarts HA; a Supervisor partial restore overwrites only the selected archive parts; deletion destroys a recovery point; a Core/OS upgrade is high-impact and its recovery path IS the pre-upgrade backupbackups
The same non-trivial Jinja expression repeated across templatesOnce a native trigger/condition and a built-in helper are ruled out, define it once as a macro in config/custom_templates/*.jinja and import itOne definition to fix when the rule changes, instead of copies that drift aparttemplate-guidelines #reusable-macros
trigger, this, value_json, or a {% set %} variable used inside an imported macroPass it to the macro as an argumentAn import does not carry the caller's context — the variable is undefined inside the macro, so it renders empty and any attribute access on it errors (HA's own functions like states are globals and do work)template-guidelines #imports-do-not-carry-the-callers-context

Reference Files

Read these when you need detailed information:

FileWhen to read
safe-refactoringRenaming entities or their display names, replacing helpers, restructuring automations, or any modification to existing config
automation-patternsWriting triggers, conditions, waits, variables, or choosing automation modes; capturing action responses; documenting/annotating steps; disabling automations; continue_on_error, admin-only actions (Unauthorized) in scripts, stopping a sequence, repeat, if/then vs choose, parallel, trigger IDs
helper-selectionDeciding whether to use a built-in helper vs template sensor — aggregation, rate of change, thresholds, time-in-state, counting/timing, scheduling, grouping, probabilistic inference, smoothing, climate, domain conversion, decision matrix
template-guidelinesConfirming templates ARE appropriate for a use case; sharing Jinja logic between templates with custom_templates macros
yaml-only-integrationsCreating or editing YAML-only integrations that have no config flow (e.g. command_line, platform-based mqtt, rest)
device-controlWriting actions, button/remote automations, or using target:
scenesAuthoring or activating scenes; snapshot/restore patterns; snapshot-vs-script distinction
dashboard-guideDesigning or modifying Lovelace dashboards — layout, view types, strategies, sections, cards, badges, CSS styling, HACS
dashboard-cardsLooking up available card types or fetching card-specific documentation
domain-docsLooking up integration/domain documentation, or the dedicated doc page for a specific trigger, condition, or action
examples.yamlNeed compound examples combining multiple best practices
appdaemonAppDaemon apps: when to use vs. native HA, app structure, actions, scheduling, error handling, safe refactoring impact
blueprint-guideAuthoring reusable blueprints: metadata & source_url, inputs & selectors, target vs entity, defaults, input sections, !input templating, versioning
backupsDeciding whether an operation needs a backup first; choosing between a full restore, a partial restore, and rolling one object back; what an archive actually contains; encryption keys and the emergency kit; restore verification; what HA does and does not protect when deleting a backup; whether a git config repo replaces a full backup
Discovery
Tags

No tags published for this skill.

Version
Latest version metadata

Version

v2026.09.24

Published

Sep 24, 2026

Category

Uncategorized

License

MIT

Source path

skills/home-assistant-best-practices

Default branch

main

Latest commit

bc73bda

Tree SHA

5963239