Skip to content

Home Assistant Scripts: Reuse Actions Without Duplicating YAML

16/10/2026
Home Assistant scripts for reusing YAML actions

This article may contain affiliate links. If you buy through these links, the price is the same for you and the store pays me a small commission that helps keep Tecnoyfoto running.

← Chapter 6 · Course index

An automation responds to an event. A script, by contrast, defines a sequence that Home Assistant runs when something calls it. The caller can be an automation, a dashboard button, Assist, Tools, or another script.

Separating those responsibilities helps avoid duplicated actions. If “prepare the living room” means turning on the light and fan, we do not need to copy those two steps into every automation that may request the routine. We can define it once, test it independently, and call it from several places.

In this chapter, we will create the course’s first script, run it manually, and replace multiple copies of one sequence with calls to a single script entity.

Automation and script: who decides when to start

An automation contains triggers:

triggers:
  - trigger: state
    entity_id: binary_sensor.course_front_door
    from: "off"
    to: "on"

That block allows Home Assistant to start it automatically when the change occurs.

A script does not need a trigger. It contains a sequence that is available to callers:

sequence:
  - action: light.turn_on
    target:
      entity_id: light.course_living_room

We can summarize the difference:

Automation: When X occurs, it executes a sequence.
Script: When you call me, run this sequence.

Both use the same action syntax. Everything we learned about choose, conditions inside a sequence, waits, and run modes can also appear in a script.

DEALS · OFFICIAL STORE

Discounts on SONOFF smart home

Wi-Fi switches, relays, sensors, LED strips and more. Deals change frequently in the official store.

Coupon: TECNOYFOTO (10% off at checkout)

View official deals Affiliate link Affiliate link · SONOFF store

Recognize a repeated sequence worth extracting

Imagine two automations:

  • When you open the door at night with someone at home, prepare the living room.
  • When someone arrives home, prepare the living room.

They both end up like this:

actions:
  - action: light.turn_on
    target:
      entity_id: light.course_living_room
  - action: switch.turn_on
    target:
      entity_id: switch.course_fan

Copying works at first. The problem appears when the routine should stop turning on the fan or needs another action. We would have to remember every copy and update all of them consistently.

A script creates a single place where that decision lives.

Create the script from the interface

Open Settings → Automations & scenes → Scripts and create a new script. Use:

  • Name: YAML Course - Prepare the living room.
  • Description: Turn on the virtual living-room light and fan.
  • Mode: single.

Add these actions in order:

  1. Turn on light.course_living_room.
  2. Turn on switch.course_fan.

Save the script. Home Assistant will create an entity similar to:

script.yaml_course_prepare_the_living_room

The exact identifier depends on the generated name and can be changed in the entity settings. Verify it before copying any course calls. To keep the practice files predictable, we will use:

script.course_prepare_living_room

If your script created from the interface has another entity_id, replace it in the examples.

The YAML of the script

In the editor’s YAML view, the content is equivalent to:

alias: "YAML Course - Prepare the living room"
description: "Turn on the virtual living-room light and fan."
icon: "mdi:sofa"
sequence:
  - action: light.turn_on
    target:
      entity_id: light.course_living_room
  - action: switch.turn_on
    target:
      entity_id: switch.course_fan
mode: single

sequence serves the same role that actions serves in an automation: it contains the steps that run from top to bottom.

alias, description, icon, and mode describe and control the script. There is no triggers section because the script does not decide when to start.

The same script in scripts.yaml

scripts.yaml is a map. The outer key identifies the script:

course_prepare_living_room:
  alias: "YAML Course - Prepare the living room"
  description: "Turn on the virtual living-room light and fan."
  icon: "mdi:sofa"
  sequence:
    - action: light.turn_on
      target:
        entity_id: light.course_living_room
    - action: switch.turn_on
      target:
        entity_id: switch.course_fan
  mode: single

Note the difference with automations.yaml:

  • automations.yaml contains a list; each automation starts with a dash.
  • scripts.yaml contains a map; each script starts with a key like course_prepare_living_room:.

Pasting one structure into the other cannot be fixed by moving spaces around. Their outer containers are different.

Manual script keys use lowercase letters and underscores. They must not contain uppercase letters or hyphens.

Test the script without any automation

Before reusing the script, test it as a standalone unit:

  1. Turn off the virtual light and fan.
  2. Open the script from the interface.
  3. Run the sequence.
  4. Check the states of both entities.
  5. Open the script trace and check the two steps.

You can also call it from Settings → Tools → Actions by selecting its direct action:

action: script.course_prepare_living_room

If it fails here, there is no need to inspect the automations that will call it. The problem is inside the script or one of the entities it controls.

Independent testing is another advantage of reuse: besides reducing duplication, it narrows the problem during diagnosis.

Call the script directly from an automation

The door automation can replace the two copied actions with a call:

alias: "YAML Course - Prepare the living room when the door opens"
triggers:
  - trigger: state
    entity_id: binary_sensor.course_front_door
    from: "off"
    to: "on"
conditions:
  - condition: state
    entity_id: binary_sensor.course_night
    state: "on"
  - condition: state
    entity_id: binary_sensor.course_someone_home
    state: "on"
actions:
  - action: script.course_prepare_living_room
mode: single

The automation remains responsible for deciding when: the door opened, it is night, and someone is home. The script decides what “prepare the living room” means.

A direct call waits for the script to finish. If the script contains a one-minute delay, the automation remains at that step for the full minute. If the script stops because of an unhandled error, the calling sequence may stop as well.

Start a script without waiting with script.turn_on

There is another way:

actions:
  - action: script.turn_on
    target:
      entity_id: script.course_prepare_living_room

script.turn_on starts the script, and the automation continues without waiting for it to finish. Errors that occur later in the called script do not stop the sequence that started it.

This is not merely alternate syntax for the same behavior; it changes the relationship between the two runs:

CallWait for completion?Can a script error stop the caller?
action: script.course_prepare_living_roomYesYes
action: script.turn_onNoNo

Use the direct call when the next step depends on the script being finished. Use script.turn_on when you just want to start it and the two sequences can continue independently.

Our example has no later steps, so both forms produce a similar visible result. We prefer the direct call because it expresses a dependent sequence and is easy to follow in the trace.

Reuse the script from a second automation

Now we can prepare the living room when someone arrives home:

alias: "YAML Course - Prepare the living room when someone arrives"
triggers:
  - trigger: state
    entity_id: binary_sensor.course_someone_home
    from: "off"
    to: "on"
conditions: []
actions:
  - action: script.course_prepare_living_room
mode: single

The two automations have different triggers and conditions, but share the same high-level action.

If we remove the fan action from the script, both automations adopt the change. A third manual call from a dashboard would use the updated routine as well.

Don’t turn every action into a script

Reuse does not mean wrapping every action in a script:

sequence:
  - action: light.turn_on
    target:
      entity_id: light.course_living_room

A script that merely renames a one-time action can add navigation without adding clarity. A script usually makes sense when at least one of these situations applies:

  • The sequence is used from several places.
  • It represents a household operation with its own name, such as “prepare the room” or “close up the house.”
  • It needs to be tested and kept separate.
  • It hides details the caller does not need to know.
  • It will accept different data to reuse the same logic.

There may also be a valid reason to keep two similar copies: they may happen to match today while representing behaviors that will evolve independently. The decision depends on responsibility, not just line count.

Input fields: the next level of reuse

A script can declare fields that receive data when it is called: a message, time, entity, or option. Home Assistant uses that information to display appropriate controls in the interface.

The general structure is:

fields:
  example_value:
    name: "Example value"
    description: "Data the script will receive."
    required: true
    selector:
      text:

When you run the script, example_value will be available as a variable. To insert it into an action, we will need a template:

{{ example_value }}

We will not introduce that syntax halfway through this chapter. In the next chapter, we will separate YAML from Jinja and then return to fields with a clear understanding of which layer contributes each part.

  • YAML defines the field and organizes the sequence.
  • Home Assistant delivers the value as a variable.
  • Jinja inserts or transforms the value inside an action.

Script run modes

A script can also be called again while it is still active. mode has the same purpose as in automations:

  • single: reject a new call.
  • restart: cancels the previous run and starts another one.
  • queued: keep calls in order.
  • parallel: runs independent calls at the same time.

Our script finishes immediately, so single is sufficient. If “prepare the living room” later included waits, we would need to decide whether a second call should be ignored, restart the routine, or enter a queue.

The automation’s mode and the script’s mode are separate decisions. The automation manages its runs; the script manages its own.

Deliberate error: update one copy and forget the other

Imagine that we left the duplicate actions and removed the fan only from the door automation. The arrival automation would still turn it on. No validator would report an error: the two configurations are correct, but the overall behavior is no longer consistent.

This is a maintenance error, not syntax. Extracting the sequence to a script eliminates the possibility that those specific copies diverge.

The design change is easiest to see as a replacement:

Before: each automation knows the two entities and actions.
After: each automation knows one household operation: prepare the living room.

Exercise: extract a repeated sequence

Start with two automations that contain these actions:

actions:
  - action: light.turn_on
    target:
      entity_id: light.course_living_room
  - action: switch.turn_on
    target:
      entity_id: switch.course_fan

Your task is to:

  1. Create script.course_prepare_living_room.
  2. Move the two actions into its sequence.
  3. Test the script independently.
  4. Replace the sequence of each automation with a direct call.
  5. Remove temporarily the fan action in the script and check that both automations adopt the change.
  6. Restore the action at the end of the test.
See the explained solution

In scripts.yaml, the script is:

course_prepare_living_room:
  alias: "YAML Course - Prepare the living room"
  description: "Turn on the virtual living-room light and fan."
  icon: "mdi:sofa"
  sequence:
    - action: light.turn_on
      target:
        entity_id: light.course_living_room
    - action: switch.turn_on
      target:
        entity_id: switch.course_fan
  mode: single

Each automation keeps its own triggers and replaces the two actions with:

actions:
  - action: script.course_prepare_living_room

The direct call is appropriate because preparation is part of the automation’s sequence. Even though the script finishes quickly now, the call makes it explicit that the step is not complete until the script finishes.

What should you take from this chapter?

An automation uses triggers to decide when to start. A script defines a sequence that runs when something calls it. Both share the syntax for actions, conditions, waits, and modes.

Extracting a repeated sequence lets you test it separately and maintain one definition. A direct call to script.name waits for completion; script.turn_on starts it without waiting.

Reuse must express an understandable operation, not split the configuration into scripts just for the sake of it. In the next chapter we will add dynamic values. Before using them, we will clearly separate YAML and Jinja so that a template does not seem another arbitrary rule of the same language.