Home Assistant Best Practices logo

Home Assistant Best Practices

Organization
homeassistant-ai
home-assistant-best-practices

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

Overview

Publisherhomeassistant-ai
Repositoryskills
Skill namehome-assistant-best-practices
Stars
807
Forks
33
Bundled files
15
LicenseMIT
Links
  • Markdown instructions

    A SKILL.md file the model loads on demand, so it only costs tokens when a request actually matches.

  • Works with any LLM

    AI skills are plain Markdown, not provider-specific code, so this works with GPT, Claude, Gemini, Grok, or a local model.

  • 15 bundled files

    Scripts, templates, and references the model can read while it works. Files are read-only and never executed.

  • Open source

    Published by homeassistant-ai on GitHub. Read the source before you install it.

Installation

Install the Home Assistant Best Practices AI skill in TypingMind to use it with any LLM, or drop it into another agent that reads SKILL.md.

1

Install in TypingMind

TypingMind installs a skill straight from its GitHub folder — it reads SKILL.md, bundles the resource files, and stores the result locally.

  1. Open the app and go to Plugins → Skills.
  2. Choose "Install from GitHub".
  3. Paste the skill folder URL below and confirm.
    https://github.com/homeassistant-ai/skills/tree/main/skills/home-assistant-best-practices
  4. Enable the skill in any chat where you want it available.
Plugins → Skills → Add skill → From GitHub URL, then paste the folder URL and press Continue.
2

Install in another agent

Any agent that reads the Agent Skills format can use this skill — copy the folder into that agent's skills directory.

Claude Code — .claude/skills
git clone --depth 1 https://github.com/homeassistant-ai/skills.git /tmp/skills
mkdir -p .claude/skills
cp -r /tmp/skills/skills/home-assistant-best-practices .claude/skills/home-assistant-best-practices
Restart Claude Code after copying so it picks up the new skill.

Use it in TypingMind

Enable Home Assistant Best Practices in any TypingMind chat and the model takes it from there. Its name and description sit in the system prompt, and the moment a request matches, the model loads the full instructions itself — you never invoke it by hand, and it costs no tokens until it is actually used.

The model loads Home Assistant Best Practices on its own as soon as a request matches it.

Works with any AI model

AI skills are plain Markdown instructions rather than provider-specific code, so Home Assistant Best Practices is not tied to the model it was written for. Install it once in TypingMind and use it with GPT-5, Claude, Gemini, Grok, DeepSeek, Mistral, Llama, or a local model you run yourself — all on your own API keys.

  • Loaded only when it is needed

    The system prompt carries just the name and description. The instructions are fetched on the first matching request, so an idle skill costs nothing.

  • Switch models mid-chat

    Because the skill is instructions rather than code, changing model does not break it — the next model reads the same SKILL.md.

Skill instructions

This is the SKILL.md content the model loads. Read it before installing — a skill is instructions your model will follow.

Home Assistant Best Practices

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

No tool reaches the HA API? Do not stop to ask for access. If you can drive a browser on the HA web UI, make the change there, but first show the user the exact change and wait for their OK before you save: approving a click does not show them what it writes. Otherwise give the user the complete config to create in the UI (for an automation, the YAML for the editor's Edit in YAML mode; for a helper, its form fields), and name the entity IDs you assumed. Config that can only be written in YAML is edited with the File editor app (or in the config folder on a Container install): keep a copy of the file first, then check the configuration and run the matching reload in the YAML tab of Tools (named Developer Tools before 2026.8; restart if it isn't listed).

A feature marked with a release (2026.5+) depends on the instance's version. Read it before choosing: version in GET /api/config, or Settings → About in the UI. On that release or later, use the feature and do not offer the older way as an equal choice. If you cannot read the version, assume a current release and name the older fallback with the version it needs.

Read the matching reference before you answer. This page only summarizes the files in Reference Files; the exact keys, fields and working examples are in them. Read every file whose row matches the task, and only those: do not load all reference files upfront.

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. Read triggers-and-conditions #purpose-specific-triggers--conditions-default-since-20267 before writing the trigger.

A purpose-specific trigger takes entity_id in its target: as well, so one known sensor is not a reason to fall back to trigger: state. Write the automation with the purpose-specific trigger; do not offer it only as an optional upgrade.

Common substitutions:

  • trigger: state on a motion, occupancy or door binary_sensor → motion.detected/motion.cleared, occupancy.detected/occupancy.cleared or door.opened/door.closed with target: {entity_id: ...}, or area_id when the area is known
  • List of individual sensor entities in a trigger → one purpose-specific trigger with an area/floor/label target:
  • delay after motion.detected for "no motion anywhere for N minutes" → motion.cleared with options: {behavior: all, for: ...}, which fires once every targeted sensor has been clear that long (unavailable and unknown sensors are left out of the count)
  • {{ 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

Read helper-selection before creating a template sensor.

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-actions #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 (see the anti-pattern row below)
  • 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

Read device-control #buttonremote-patterns before writing the trigger.


Critical Anti-Patterns

Anti-patternUse insteadWhyReference
condition: template with float > 25condition: numeric_stateValidated at load, not runtimetriggers-and-conditions #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-actions #wait-actions
device_id in triggersentity_id (or device_ieee for ZHA)device_id breaks on re-adddevice-control #entity-id-vs-device-id
event trigger on an integration's bus event (e.g. hue_event) for a button that has an event.* entityevent.received targeting that entity, with values read from its event_types attributeThe entity can be renamed and survives a re-add when the integration keeps a stable unique ID; bus event data differs per integrationdevice-control #buttonremote-patterns
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)triggers-and-conditions #unavailable-arms-a-numeric-state-trigger
mode: single for motion lightsmode: restartRe-triggers must reset the timerautomation-actions #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-actions #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 Min/Max helpers or custom Config-Entry integrations (e.g. Better Thermostat) without checking their Config-Entry dataScan the config entries' data+options; re-select the renamed entity in the Min/Max Options Flow. An integration that keeps it in data (Better Thermostat) has no API fix: tell the user and take a full backup before the renameThey store entity_ids in the Config Entry, and a registry rename does not update them; single-source helpers such as Threshold (since 2025.6) and Generic Thermostat update themselvessafe-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 programmatically; with no API access, give the config for the UI editor insteadAPI calls validate config, avoid syntax errors, and don't require manual file edits or restartstriggers-and-conditions, automation-actions, 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 IDs in cleaning_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_kelvin with the value converted: Kelvin = 1,000,000 ÷ mireds (500 mireds = 2000 K)The color_temp parameter was removed in 2026.3; only Kelvin is supported, and HA accepts any positive number there, so a mireds value copied across is read as Kelvin (500 mireds becomes 500 K)device-control #lights
Saving states or attributes in variables:, or hard-coding values, to put devices back after a temporary changescene.create with snapshot_entities before the change, then scene.turn_on on that sceneThe snapshot keeps each entity's own state, including a light that was off, in one call; variables need a template per attribute and a restore step per entityscenes #snapshot--restore-scenecreate
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 replacementstriggers-and-conditions #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 liststriggers-and-conditions #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/all (renamed 2026.6)Old keys no longer load; old behavior values raise a repair issue and face removaltriggers-and-conditions #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 each file whose row matches the task before you answer:

FileWhen to read
safe-refactoringRenaming entities or their display names, replacing helpers, restructuring automations, or any modification to existing config
triggers-and-conditionsWriting an automation's triggers or conditions: purpose-specific and native triggers/conditions, for: durations, trigger IDs
automation-actionsWriting the actions of an automation or script, or choosing its mode: waits, variables, capturing action responses, continue_on_error, admin-only actions (Unauthorized) in scripts, stopping a sequence, repeat, if/then vs choose, parallel; documenting/annotating steps; disabling automations
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, including putting devices back the way they were after a temporary change; 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

Bundled files

The model reads these on demand while the skill is loaded. They are exposed as readable files and are never executed.

Frequently asked questions

What does the Home Assistant Best Practices AI skill do?

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 - Entit...

Why use Home Assistant Best Practices on TypingMind?

Because you install it once and use it with any model. Home Assistant Best Practices is plain Markdown rather than provider-specific code, so the same skill runs on GPT-5, Claude, Gemini, Grok, or a local model — and you can switch model mid-chat without it breaking. TypingMind runs on your own API keys, so you pay providers directly instead of a per-seat subscription, and your skills and chats stay in your own storage.

How do I install Home Assistant Best Practices in TypingMind?

Open Plugins → Skills → Install from GitHub in TypingMind and paste https://github.com/homeassistant-ai/skills/tree/main/skills/home-assistant-best-practices. TypingMind reads its SKILL.md and bundles its files and installs it as a skill you can enable per chat.

Which AI models can use Home Assistant Best Practices?

Any model you connect in TypingMind. AI skills are plain Markdown instructions rather than provider-specific code, so GPT, Claude, Gemini, Grok, and local models can all load this skill when a request matches it.

How many AI models can I use with Home Assistant Best Practices?

As many as you like. As long as a model supports skills, you can use Home Assistant Best Practices with it — GPT, Claude, Gemini, Grok, DeepSeek, Mistral, Llama and more — all on TypingMind with your own API keys.

Is the Home Assistant Best Practices AI skill free?

Yes. It is published on GitHub by homeassistant-ai under the MIT license. You only pay your own AI provider for the tokens you use.

What are AI skills?

An AI skill is a reusable instruction bundle that teaches an AI model how to do one specific task. It follows the open Agent Skills format: a SKILL.md file with a name and description, plus any scripts, templates or reference files the model may need. The model reads the instructions only when your request matches the skill, so an installed skill costs nothing until it is used.

How are AI skills different from plugins or MCP servers?

A plugin or MCP server gives a model new tools to call — code that runs somewhere and returns a result. An AI skill gives the model knowledge and process instead: how to approach a task, which steps to follow, what good output looks like. Skills are plain Markdown, so they need no server, no API key and no runtime, and they work with any model.

View all

Set up your own AI workspace now

Get notified about new features and future giveaways by subscribing to our newsletter 👇