Create or Update Automation
ha_config_set_automationCreate, update, or take control of Home Assistant automations. Use full configs, Python transforms, or blueprint inputs to define triggers, conditions, and actions.
Instructions
Create or update a Home Assistant automation.
MUST call ha_get_skill_guide OR refer to your locally installed skills first.
PREFER NATIVE SOLUTIONS OVER TEMPLATES (read this before writing any {{ ... }}):
Native triggers/conditions/actions are validated at config load, fail loudly, and
do not bypass HA's schema. Templates fail silently at runtime and obscure intent.
condition: numeric_stateinstead of{{ states('x') | float > N }}condition: state(withstate:list) instead of{{ is_state(...) }}/{{ states(x) in [...] }}condition: timeinstead of{{ now().hour ... }}or{{ now().weekday() ... }}condition: suninstead of{{ is_state('sun.sun', ...) }}Native
for:field onstate/numeric_statetriggers andstateconditions over{{ now() - X.last_changed > timedelta(...) }}duration math.wait_for_triggerinstead ofwait_templatechooseaction instead of template-based service namesFor one-shot date firing, use a
timetrigger plusautomation.turn_offon a hardcoded entity_id — not{{ now().date() ... }}.Hardcode
target.entity_idliterals — never{{ this.entity_id }}. Templates are appropriate ONLY indata.*fields, notification message/title,event_data, andvariables. The reactive best-practice checker on this tool will surface anything in a logic position that should be native; consult thebest_practice_warningsfield on the response and fix before re-submitting. The relevant skill section is auto-embedded underskill_contenton warnings, and the fullautomation-patterns.md+template-guidelines.mdreferences ship underskill_contentproactively by default. For comprehensive guidance beyond that, callha_get_skill_guide.
The returned automation_id is the resolved entity_id (canonical
form, e.g. automation.morning_routine) when entity registration
succeeds, falling back to the input identifier (update path) or
the generated unique_id from the upsert response (fresh create
when no identifier was passed).
Before reaching for ha_config_set_automation, consider whether a
dedicated tool fits the use case better:
State snapshot of one or more entities (capture-then-replay, no trigger needed) -> ha_config_set_scene
State-derived value that recomputes when its inputs change (template sensor / binary sensor / number / select) -> ha_config_set_helper(helper_type='template')
Stateful counter / timer / schedule / boolean / etc. -> ha_config_set_helper(helper_type='counter' | 'timer' | ...)
Supports three modes: full config replacement, Python transformation, or take_control_of_blueprint (see below).
WHEN TO USE WHICH MODE:
python_transform: RECOMMENDED for edits to existing automations. Surgical updates.
config: Use for creating new automations or full restructures.
take_control_of_blueprint: converts a blueprint-backed automation into a standalone one. Takes no config of its own.
IMPORTANT: python_transform requires 'identifier' and 'config_hash' from ha_config_get_automation().
PYTHON TRANSFORM EXAMPLES (operate on the fetched config, which uses HA's canonical plural root keys 'triggers'/'actions'/'conditions'):
Update action: python_transform="config['actions'][0]['data']['brightness'] = 255"
Add trigger: python_transform="config['triggers'].append({'trigger': 'state', 'entity_id': 'binary_sensor.motion', 'to': 'on'})"
Remove last action: python_transform="config['actions'].pop()"
Omit identifier and config['id'] to create a new automation with a generated ID. A previously unused raw ID can also create an automation with that specific ID. Reusing an identifier targets the same automation, even if the alias changes. To intentionally rename or replace it, first read it with ha_config_get_automation and pass its config_hash. A changed alias without that hash is rejected before writing.
AUTOMATION TYPES:
Regular Automations - Define triggers and actions directly
Blueprint Automations - Use pre-built templates with customizable inputs
REQUIRED FIELDS (Regular Automations):
alias: Human-readable automation name
triggers: List of triggers (time, state, event, etc.)
actions: List of actions to execute
REQUIRED FIELDS (Blueprint Automations):
alias: Human-readable automation name
use_blueprint: Blueprint configuration
path: Blueprint file path (e.g., "motion_light.yaml")
input: Dictionary of input values for the blueprint
OPTIONAL CONFIG FIELDS (Regular Automations):
description: Detailed description of the user's intent (RECOMMENDED: helps safely modify implementation later)
category: Category ID for organization (use ha_config_get_category to list, ha_config_set_category to create)
conditions: Additional conditions that must be met
mode: 'single' (default), 'restart', 'queued', 'parallel'
max: Maximum concurrent executions (for queued/parallel modes)
initial_state: Whether automation starts enabled (true/false)
variables: Variables for use in automation
BASIC EXAMPLES:
Simple time-based automation: ha_config_set_automation(config={ "alias": "Morning Lights", "description": "Turn on bedroom lights at 7 AM to help wake up", "triggers": [{"trigger": "time", "at": "07:00:00"}], "actions": [{"action": "light.turn_on", "target": {"area_id": "bedroom"}}] })
Motion-activated lighting — for: on the off-transition replaces action-delay:
ha_config_set_automation(config={
"alias": "Motion Light",
"triggers": [
{"trigger": "state", "entity_id": "binary_sensor.motion", "to": "on", "id": "motion_on"},
{"trigger": "state", "entity_id": "binary_sensor.motion", "to": "off",
"for": {"minutes": 5}, "id": "motion_off"}
],
"actions": [
{"choose": [
{"conditions": [
{"condition": "trigger", "id": "motion_on"},
{"condition": "sun", "after": "sunset"}
],
"sequence": [{"action": "light.turn_on", "target": {"entity_id": "light.hallway"}}]},
{"conditions": [{"condition": "trigger", "id": "motion_off"}],
"sequence": [{"action": "light.turn_off", "target": {"entity_id": "light.hallway"}}]}
]}
]
})
Update existing automation: current = ha_config_get_automation(identifier="automation.morning_routine") ha_config_set_automation( identifier="automation.morning_routine", config_hash=current["config_hash"], config={ "alias": "Updated Morning Routine", "triggers": [{"trigger": "time", "at": "06:30:00"}], "actions": [ {"action": "light.turn_on", "target": {"area_id": "bedroom"}}, {"action": "climate.set_temperature", "target": {"entity_id": "climate.bedroom"}, "data": {"temperature": 22}} ] } )
BLUEPRINT AUTOMATION EXAMPLES:
Create automation from blueprint: ha_config_set_automation(config={ "alias": "Motion Light Kitchen", "use_blueprint": { "path": "homeassistant/motion_light.yaml", "input": { "motion_entity": "binary_sensor.kitchen_motion", "light_target": {"entity_id": "light.kitchen"}, "no_motion_wait": 120 } } })
Update blueprint automation inputs: ha_config_set_automation( identifier="automation.motion_light_kitchen", config={ "alias": "Motion Light Kitchen", "use_blueprint": { "path": "homeassistant/motion_light.yaml", "input": { "motion_entity": "binary_sensor.kitchen_motion", "light_target": {"entity_id": "light.kitchen"}, "no_motion_wait": 300 } } } )
TAKE CONTROL OF A BLUEPRINT AUTOMATION:
take_control_of_blueprint=True converts a blueprint-backed automation into a standalone one — the UI's "Take control". The blueprint is rendered with the automation's CURRENT inputs and the result is saved over the same automation, which keeps its entity_id, alias and description but gains its own triggers/conditions/actions and loses 'use_blueprint'.
ha_config_set_automation( identifier="automation.motion_light_kitchen", take_control_of_blueprint=True, )
This is one-way: the automation is no longer linked to the blueprint, so later blueprint edits stop reaching it. To change an input value, update 'use_blueprint.input' instead (see the example above) — that keeps the link.
Taking control does NOT free the blueprint. Home Assistant goes on counting a converted automation as a user of it, so deleting that blueprint stays refused until the automation itself is removed (verified against Home Assistant 2026.9; an automation reload does not clear it either).
The response names the blueprint in took_control_of_blueprint. To see what the rendering looks like WITHOUT writing
anything, call ha_manage_blueprints(action="substitute", path=...,
input=...), which returns the config and leaves the automation alone.
ha_manage_blueprints also lists, imports, saves and deletes blueprints,
and action="get" reports which automations use one.
TRIGGER TYPES: time, time_pattern, sun, state, numeric_state, event, device, zone, template, and more CONDITION TYPES: state, numeric_state, time, sun, template, device, zone, and more ACTION TYPES: action calls, delays, wait_for_trigger, wait_template, if/then/else, choose, repeat, parallel
For comprehensive automation documentation with all trigger/condition/action types and advanced examples:
Use: ha_get_skill_guide
TROUBLESHOOTING:
Use ha_get_state() to verify entity_ids exist
Use ha_search() to find correct entity_ids
IF you must use Jinja2 and have no native alternative, test it first with ha_eval_template() before embedding it in the automation config — catches syntax errors and unresolved entity_ids before they fail silently at runtime
Use ha_search(domain_filter='automation') to find existing automations
create update modify edit automation triggers conditions actions new automation write save take control blueprint detach unlink standalone convert
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| wait | No | Wait for automation to be queryable before returning. Default: True. Set to False for bulk operations. | |
| config | No | Complete automation configuration with required fields: 'alias', 'triggers', 'actions'. Optional: 'description', 'conditions', 'mode', 'max', 'initial_state', 'variables'. Purpose-specific triggers/conditions (HA 2026.7+ default: 'trigger': '<domain>.<name>' with 'target'/'options') are valid config. Mutually exclusive with python_transform. | |
| category | No | Category ID to assign to this automation. Use ha_config_get_category(scope='automation') to list available categories, or ha_config_set_category() to create one. | |
| identifier | No | Target automation entity_id or HA config 'id' (unique_id). Omit for creation with a generated ID. Values such as 'new' are literal IDs, not placeholders. Required for python_transform. | |
| config_hash | No | Config hash from ha_config_get_automation for optimistic locking. REQUIRED for python_transform (validates automation unchanged). Required when a config update changes an existing automation's alias. Otherwise optional for config updates (validates before full replacement if provided). | |
| MandatoryBPS | No | ||
| BestPracticeKey | No | Read-receipt for the home-assistant-best-practices skill; required when strict best-practices mode is enabled. Not a secret or credential: the current value is an attestation phrase published openly at the top of the skill content served by ha_get_skill_guide. Read that content, then pass the value back verbatim — this round-trip is the server's designed protocol confirming the practices were read before writing. | |
| python_transform | No | Python expression to transform existing automation config. Mutually exclusive with config. Requires identifier and config_hash for validation. WARNING: Expressions with infinite loops will hang the server. Examples: Simple: python_transform="config['actions'][0]['data']['brightness'] = 255" Pattern: python_transform="for a in config['actions']: if a.get('alias') == 'My Step': a['data']['value'] = 100" PYTHON TRANSFORM SECURITY: ✅ ALLOWED: - Dictionary/list access: config['views'][0]['cards'][1] - Slicing: config['views'][0]['cards'][1:3] - Assignment: config['key'] = 'value' - Deletion: del config['key'] or config.pop('key') - List methods: append, insert, pop, remove, clear, extend - Dict methods: update, get, setdefault, keys, values, items - Loops: for, if/else, pass, break, continue - Comprehensions: [x for x in ...], {k: v for ...}, (x for x in ...) - Ternary: x if condition else y - Iterable unpacking (* in calls/literals): f(*xs), [*xs, y] - Dict unpacking (**) in calls and dict literals: {**d, 'k': v} - Keyword arguments: func(key=value) - Lambdas (e.g. for `key=`): sorted(items, key=lambda x: x['score']) - String methods: startswith, endswith, lower, upper, strip, split, join, replace - Safe builtins: isinstance, len, range, enumerate, zip, sorted, reversed, min, max, sum, abs, any, all, round, str, int, float, bool, list, dict, tuple, set ❌ FORBIDDEN: - Imports: import, from, __import__ - File operations: open, read, write - Dunder access: __class__, __bases__, __subclasses__ - Dangerous builtins: eval, exec, compile, getattr, setattr, delattr, hasattr - Function definitions: def, class - Exception handling: try/except (validate with isinstance/in/.get() instead) - While loops: use bounded for loops or comprehensions instead 🎯 PATTERNS: - Filter cards: cards = [c for c in cards if keep(c)] - Skip in a loop: prefer `continue` over an empty `pass` branch (clearer) - Conditionally include: build a new list and `.append(x)` only the cards you want, instead of iterating the original and using if/pass branches to drop entries - Modify in place when possible (single pass, fewer surprises) over reconstructing the entire list | |
| take_control_of_blueprint | No | Convert a blueprint-backed automation into an editable standalone one -- the UI's "Take control". Renders the blueprint with its current inputs and saves the result over the same automation, which then has its own triggers/conditions/actions and no 'use_blueprint'. Requires identifier; mutually exclusive with config and python_transform. Irreversible: the link to the blueprint is gone afterwards, so edit inputs instead if you only want to change a value. Does NOT free the blueprint: Home Assistant keeps counting the converted automation as a user, so deleting that blueprint stays refused until the automation is removed. To preview the rendering without writing anything, use ha_manage_blueprints(action="substitute"). |
Output Schema
| Name | Required | Description | Default |
|---|---|---|---|
No arguments | |||