How to Write Home Assistant Automations in YAML
How to write Home Assistant automation YAML using triggers, conditions, actions, modes, current syntax, and a complete working example.
Learning how to write Home Assistant automations YAML gets you past the point where the visual editor starts fighting you: nested conditions, nine-line templates, or an automation you want to copy-paste into version control instead of clicking through a dozen dropdown menus. YAML is the same engine the UI editor writes to behind the scenes, so nothing here is more “advanced” than clicking buttons, it’s just faster to read and easier to reuse once you know the shape of it.
The three-part structure every automation follows
Every Home Assistant automation, however it’s written, boils down to three blocks, as documented in the official YAML automation guide:
- Triggers (required): the event(s) that start the automation. Multiple triggers are OR’d together: any one of them firing starts the automation.
- Conditions (optional): checks that must pass before the actions run. Multiple conditions are AND’d together by default.
- Actions (required): what actually happens: turning on a light, calling a script, sending a notification.
As of Home Assistant 2024.10, the top-level keys are plural: triggers, conditions, actions; the platform key inside a trigger definition was renamed to trigger. The 2024.10 release notes are explicit that this is not a breaking change: old trigger:/condition:/action: (singular) and platform: still work, and the UI automatically rewrites automations to the new syntax when you save them through the editor. If you’re following an older tutorial that uses platform: state, it’ll still run; new automations should just use trigger: state.
A minimal automation looks like this:
alias: "Porch light at sunset"
description: "Turn on the porch light 30 minutes before sunset"
triggers:
- trigger: sun
event: sunset
offset: "-00:30:00"
conditions: []
actions:
- action: light.turn_on
target:
entity_id: light.porch
mode: single
alias is the friendly name shown in the UI, id (not shown above) lets an automation persist state and appear in Logbook/traces, and mode controls what happens if the automation is triggered again while still running: single (default, ignore restarts), restart, queued, or parallel, per the YAML automation docs.
Triggers: what starts the automation
Home Assistant ships a wide set of trigger types, cataloged in the trigger documentation. The ones you’ll reach for most often:
triggers:
# fires when an entity changes state
- trigger: state
entity_id: binary_sensor.front_door
to: "on"
# fires at a fixed clock time
- trigger: time
at: "22:00:00"
# fires on a repeating pattern, e.g. every 5 minutes
- trigger: time_pattern
minutes: "/5"
# fires when a numeric sensor crosses a threshold
- trigger: numeric_state
entity_id: sensor.living_room_humidity
above: 60
# fires when a Jinja2 template flips from false to true
- trigger: template
value_template: "{{ states('sensor.washer')|int(0) == 0 }}"
Each trigger can carry its own id: so downstream actions can branch on which trigger fired, and any trigger can be temporarily disabled with enabled: false without deleting it. If you’re exposing automations to the outside world with a webhook trigger, keep the webhook ID long and random and put it behind HTTPS. That’s a general web-exposure problem, not a Home Assistant-specific one, and techsentinel.news is a decent ongoing read if you want the wider security context on exposing home services to the internet.
Conditions: gating when actions run
Conditions use the same syntax whether they’re inside an automation or a script, per the conditions reference. They default to AND logic, but you can nest and, or, and not blocks for anything more complex:
conditions:
- condition: state
entity_id: device_tracker.phone
state: "home"
- condition: or
conditions:
- condition: numeric_state
entity_id: sensor.outside_temp
below: 5
- condition: sun
after: sunset
A state condition can add a for: duration to require the state has held steady, which is the usual fix for automations that fire on a sensor blip rather than a real event.
Actions: what actually happens
Actions follow script syntax and run in order unless you branch with if/then, choose, or repeat. target groups the entity, device, or area a service call applies to; data carries the call’s parameters:
actions:
- action: light.turn_on
target:
entity_id: light.living_room
data:
brightness_pct: 60
color_name: "warm white"
- delay: "00:00:05"
- action: notify.mobile_app_phone
data:
message: "Living room lights are on."
service: still works as an alias for action: for the same backward-compatibility reason as platform: versus trigger:. If an automation’s actions include a condition: step partway through, a failed condition stops that run right there without touching anything after it.
One thing worth flagging if you’re feeding any of this into an LLM-based assist pipeline (voice control, an “ask the house” conversation agent) rather than writing it by hand: an automation’s actions can unlock doors, disarm alarms, and open garages, so treat any AI-generated action list as untrusted input that needs a review step before it runs unattended. guardml.io covers guardrail patterns for exactly that kind of AI-to-actuator gap if you’re building something more automated than a chatbot suggestion box.
A complete, runnable example
Putting triggers, conditions, and actions together: turn on the hallway light when motion is detected after dark, but only if nobody’s already marked as home and awake:
alias: "Hallway light on motion after dark"
id: "hallway_motion_after_dark"
triggers:
- trigger: state
entity_id: binary_sensor.hallway_motion
to: "on"
conditions:
- condition: sun
after: sunset
- condition: state
entity_id: input_boolean.night_mode
state: "off"
actions:
- action: light.turn_on
target:
entity_id: light.hallway
data:
brightness_pct: 40
- delay: "00:02:00"
- action: light.turn_off
target:
entity_id: light.hallway
mode: restart
mode: restart here matters: if motion re-triggers while the two-minute delay is running, the automation restarts the timer instead of queuing a second run that would turn the light off early.
Where to actually write and check this
Writing the YAML does not require a text editor on the Home Assistant host. Inside an existing or new automation, use the menu button in the top right and choose “Edit in YAML” to drop into the same editor the UI builder uses, with live validation. For automations you want in version control, Home Assistant also supports splitting automations into individual files under automations.yaml using !include_dir_merge_list, though that’s a configuration.yaml change, not something you set per-automation.
The most common first-timer mistake isn’t a syntax error, it’s YAML’s own indentation rules: entity_id and data need to line up under the action they belong to, and a stray tab (YAML doesn’t allow tabs) will throw a config error on reload rather than a helpful pointer to the line. When in doubt, use the built-in YAML editor’s validation before saving, and check Settings > System > Logs if an automation silently doesn’t fire.
Sources
Related
Best Zigbee Coordinator Stick for Home Assistant: 4 Picks
Home Assistant Connect ZBT-2 suits ZHA, while SONOFF and SMLIGHT coordinators offer options for Zigbee2MQTT, Docker, and difficult server locations.
ZHA vs Zigbee2MQTT: Differences and Tradeoffs
This comparison explains how ZHA and Zigbee2MQTT differ in setup, device support, interfaces, MQTT requirements, migration, and best-fit use cases.
How to Add Zigbee2MQTT to Home Assistant
This guide explains Zigbee2MQTT setup in Home Assistant, including Mosquitto, the add-on, adapter setup, MQTT discovery, and pairing the first device.