Master Home Assistant Advanced Templates and Conditions Like a Pro

Most Home Assistant automations break because the condition layer is wrong, not the trigger. Once you understand Jinja templates and the difference between triggers, conditions, and template sensors, every automation gets simpler, faster, and more reliable. Here's the model I teach friends after they've already given up once.

I spent two years writing Home Assistant automations badly. The triggers fired correctly, the actions worked, but the logic in between was a copy-paste mess of repeated templates and stale conditions. The unlock came from understanding why each stage exists and where to put logic that lasts. This guide walks through that model with concrete examples.

The Four Stages of a Home Assistant Automation

Every automation has four conceptual stages, even if you stuff the logic into two YAML blocks. Understanding the boundaries is what makes the difference between a one-off hack and a maintainable system.

Triggers fire on state changes. They are events. "The door opened." "The temperature crossed 25 degrees." "A button was pressed." The trigger does not evaluate whether the automation should run -- only that something happened worth checking.

Conditions evaluate state at the moment the trigger fires. They are boolean checks. "Is the home in away mode?" "Is it after 10 PM?" "Is the kettle currently off?" Conditions decide whether the action runs. They do not change state, only inspect it.

Actions do the work. They call services, change states, send notifications. An action can include template values that get rendered at execution time -- this is where the third stage, templates, hides.

Templates are Jinja expressions evaluated against the current state of Home Assistant. They appear in conditions to compute boolean values, in actions to compute service data, and in template sensors to compute derived values that other automations can use. Templates are read-only -- they cannot change state.

Build Multi-Condition Automations Without Nested AND Spaghetti

The condition list defaults to AND -- every condition must be true for the action to run. The trap is when you need OR logic or grouped conditions. The clean pattern uses the explicit condition: and and condition: or blocks:

condition:
  - condition: and
    conditions:
      - condition: state
        entity_id: input_boolean.away_mode
        state: 'on'
      - condition: or
        conditions:
          - condition: time
            after: '22:00:00'
          - condition: time
            before: '06:00:00'

That reads as: "Away mode is on AND (after 10 PM OR before 6 AM)". Nest groups three levels deep and the YAML becomes unreadable. The fix at that point is a template sensor.

A template sensor is a custom entity that exposes the boolean result of an arbitrary expression. Move complex logic into the sensor, give it a clear name, and reference it from automations:

template:
  - binary_sensor:
      - name: 'Should Run Night Lights'
        state: >-
          {{ is_state('input_boolean.away_mode', 'on')
             and (now().hour >= 22 or now().hour < 6) }}

Now the automation condition is just condition: state, entity_id: binary_sensor.should_run_night_lights, state: 'on'. The logic lives in one place. Five different automations can reuse the same sensor without copying the template.

Five Jinja Template Patterns That Work in Real Life

These are the templates I genuinely use across the smart home. Each one solves a recurring problem.

The numeric guardrail: {{ states('sensor.living_temp') | float(0) > 25 }}. The float(0) filter converts the state to a number with a default of zero if the conversion fails. Without it, a sensor reporting "unavailable" makes the entire automation crash silently. With it, the automation behaves predictably even during sensor outages.

The time-of-day branch: {% if now().hour < 7 %}5{% else %}80{% endif %}. Returns 5% brightness before 7 AM and 80% after. The same pattern works for thermostat setpoints, notification severity, and any value that should change by hour.

The state filter: {{ states.light | selectattr('state', 'eq', 'on') | list }}. Returns a list of every light entity that is currently on. Combine with | count to get a number, or with | map(attribute='entity_id') | list to get a list of IDs to act on.

The cross-entity aggregate: {{ expand('group.bedroom_temps') | map(attribute='state') | map('float') | average }}. Computes the average state across every member of a group. Useful for whole-house temperature, total power consumption, or any "rollup" metric.

The conditional notification body: {% if trigger.to_state.state == 'on' %}opened{% else %}closed{% endif %}. Inside an action's service data, you can use trigger context (trigger.to_state, trigger.from_state, trigger.entity_id) to format the notification text based on what triggered the automation. Eliminates duplicate automations for "opened" versus "closed" events.

Performance: Why Some Templates Make Home Assistant Slow

Home Assistant re-evaluates a template whenever any entity referenced in the template changes state. This is usually fine. It becomes a problem when a template references many entities or iterates over states without filtering.

A template like {{ states | selectattr('domain', 'eq', 'sensor') | list | count }} re-evaluates on every state change in the entire system. In a house with 300 entities that means dozens of evaluations per second. Multiply by ten similar templates and the CPU melts.

The fix is to scope the template to the entities you actually care about. {{ states.sensor | selectattr('state', 'eq', 'unavailable') | list | count }} only iterates over sensor-domain entities. Adding explicit entity references through the trigger_id filter narrows further:

template:
  - sensor:
      - name: 'Average Living Temp'
        state: >-
          {{ (states('sensor.living_temp') | float(0)
              + states('sensor.living_temp_2') | float(0))
              / 2 | round(1) }}

That template only re-evaluates when one of those two specific sensors changes. The performance is bounded and predictable. Get into the habit of bounding template scope and the system stays responsive at 500+ entities.

Debugging Templates the Right Way

The Developer Tools panel in Home Assistant has a Template tab. It is the single most underused debugging tool in the platform. Paste any template, see the rendered output live, edit and re-render in real time.

The workflow I use:

  1. Open Developer Tools, click Template
  2. Paste the template I'm about to use in an automation
  3. Verify it returns the expected value with real current state
  4. Change a relevant entity state in the States tab, return to Template, confirm it updates
  5. Only then drop the template into the automation YAML

That five-step loop catches 90% of template bugs before they reach an automation. The remaining 10% are timing issues that need actual automation testing. The Home Assistant templating docs cover every filter and function available; the Jinja template designer reference covers the underlying language.

A Real Multi-Condition Automation I Actually Run

Here's one of the more complex automations on my system, documented as it actually exists. It handles the morning routine and demonstrates conditions, template values, and action chaining together.

The automation triggers when the bedroom sleep tracker reports the partner is awake. It checks four conditions: it's a weekday, after 6 AM, before 9 AM, and the kitchen is not already lit. If all conditions pass, the action sequence fires: kitchen lights ramp from 5% to 80% over two minutes, the coffee maker plug switches on, a voice notification on the kitchen speaker reads the day's calendar appointments from a template, and the heating setpoint rises from the night value to the morning value through another template that varies by outdoor temperature.

The whole thing fits in about 60 lines of YAML once you have the template sensors as building blocks. Without the template sensors it would be 200 lines of inline expressions, and editing it would be a Saturday-afternoon project rather than a five-minute change.

Build a Template Library, Not One-Off Automations

The single biggest leap in Home Assistant maintainability comes from treating template sensors as a library. Build a dozen general-purpose ones up front -- house-occupied, any-window-open, total-power-now, average-bedroom-temp, sunset-in-30-mins -- and reference them across every automation that needs the value. Edit the logic in one place and every consumer updates.

Most Home Assistant configurations I review have the same template logic repeated five or six times across different automations. Consolidate into named sensors. The configuration becomes shorter, faster, and easier to maintain. That single discipline is what separates a working smart home from one that becomes too fragile to touch after 18 months.

The conditions and templates layer is where Home Assistant earns its reputation for power. The same techniques apply whether you run 30 entities or 3000. Master the four-stage model, write template sensors as a library, debug in the Template tab, and the system scales to whatever your house needs.

Advanced Patterns Worth Studying Like Code Reviewers Do

A few advanced patterns take longer to understand but pay back across the entire smart home. Treating them like code rather than scripted commands changes the result.

The first pattern is wait_template, which pauses an action sequence until a template becomes true. The classic use is "wait for the bathroom light to turn on before doing X". Use the timeout parameter so a stuck state does not freeze the automation forever. Add continue_on_timeout: false if you want the automation to abort cleanly when the wait expires.

The second pattern is choose, which is the Home Assistant equivalent of a switch statement. Multiple condition blocks each map to a different action sequence. Use it whenever an automation needs to do different things based on what triggered it, what time of day it is, or what mode the house is in. The alternative -- multiple automations all triggered by the same event with different conditions -- becomes unmaintainable after about four branches.

The third pattern is the trigger variables block. Triggers can attach variables that the conditions and actions can reference through trigger.variables. This is how you write generic automations that handle multiple sensors -- one automation triggered by ten contact sensors can take different actions per sensor without ten separate automations.

The fourth pattern is the until clause inside repeat blocks, which lets an action sequence loop until a condition becomes true. Useful for ramping a value, retrying a flaky service call, or polling for a state change with bounded iteration.

The fifth pattern is response_variable, introduced in 2023 and rarely used. Some service calls return data (calendar lookups, weather queries, API calls). The response_variable captures that data into a variable the rest of the action sequence can use. Without it you would need a separate script call and a state-based handoff. With it the data flows inline through the automation.

Each of these patterns shows up in the official Home Assistant blueprint repository. Reading other people's blueprints is the fastest way to learn idioms beyond the basics, the same way reading other developers' open-source code teaches programming idioms.

Common Mistakes I Still Make After Six Years

Honesty time. Six years into running Home Assistant I still make the same five mistakes about once a month. Naming them might save you the time it cost me.

I forget the float(0) filter on a new sensor and find out two days later when the sensor briefly disconnects and breaks three automations downstream. The fix is to make float(0) a habit on every numeric sensor reference, every time.

I write inline templates instead of template sensors when I'm in a hurry, then spend an hour debugging when something changes. The five-minute discipline of building a named sensor saves the future hour every time. Inline is fine for one-off testing; production goes through a sensor.

I forget to put a cool-down on a new automation, then watch it fire fifty times when a sensor stutters. Five-second debounce on every state-change trigger should be the default, not an afterthought.

I trust a service call return code that isn't there. Some Home Assistant integrations fire-and-forget; the action returns success even if nothing happened downstream. Verify with a follow-up state check or a wait_template when reliability matters.

I write a clever one-line template that takes me 20 minutes to read three months later. Long templates split across multiple lines with comments are friendlier than dense one-liners. Optimise for readability, not character count.