ESPHome Device Auto Bulk Update

ESPHome Device Auto Bulk Update

A blueprint that automatically handles ESPHome device firmware updates so you don't have to. Rather than relying on the ESPHome add-on's built-in "Update All" button which can be unreliable, this automation monitors your devices and handles updates on a schedule within a time window you define.


:hand_with_fingers_splayed: Usage

  1. Enable firmware update entities for each ESPHome device — Settings → Devices & Services → ESPHome → Your Device → Enable the Firmware Update entity
  2. Import the Blueprint
    Import Blueprint
  3. Create an Automation from the imported Blueprint
    Open Blueprints
  4. Configure the sliders and options to your preference — no YAML editing required

:sparkles: Features

  • Triggers automatically when a set number of devices have pending updates
  • Daily sweep at the start of your update window catches anything missed outside the window
  • Actionable notification sent before updates begin listing all affected devices by name
  • Update Now button to skip the delay and update immediately
  • Cancel button to abort if the timing isn't convenient
  • Choice of updating all devices at once or sequentially one at a time
  • Configurable delay between sequential updates
  • Completion notification confirms how many devices were updated and which ones
  • Cooldown period prevents the automation re-triggering immediately after updates complete

:wrench: Requirements

  • ESPHome integration installed and configured
  • Home Assistant Companion App installed on your notification device
  • Device firmware update entities must be enabled for each ESPHome device

:gear: Flow

  1. Triggers when device update count exceeds the threshold, or daily sweep at the start of the time window
  2. Checks the time window and device count conditions are met
  3. Sends a notification listing devices to be updated with Update Now and Cancel buttons
  4. Waits for the delay period listening for a button response
  5. If Cancel is tapped — sends a cancellation notification and stops
  6. If Update Now is tapped or delay expires — proceeds with updates (Notification remains open so Cancel remains active)
  7. Updates all devices at once or sequentially depending on mode selected
  8. Sends a completion notification listing updated devices
  9. Waits out the cooldown period before the automation can trigger again

blueprint:
  name: ESPHome Device Auto Bulk Update
  description: |
    Automatically updates ESPHome devices when a specified number of them
    have pending updates. Optionally sends a notification before updating.
    DEVICE FIRMWARE ENTITIES MUST BE ENABLED
    FLOW:
    1. Triggers when device update count exceeds the threshold, or daily sweep at the start of the time window.
    2. Checks the time window and device count conditions are met.
    3. Sends a notification listing devices to be updated with Update Now and Cancel buttons.
    4. Waits for the delay period listening for a button response.
    7. If Cancel is tapped - sends a cancellation notification and stops.
    8. If Update Now is tapped or delay expires - proceeds with updates.
    9. Updates all devices at once or sequentially depending on mode selected.
    10. Sends a completion notification listing updated devices.
    11. Waits out the cooldown period before the automation can trigger again.
  domain: automation
  source_url: https://github.com/bferd/homeassistant-blueprints/blob/main/esphome_auto_bulk_update.yaml
  input:
    update_threshold:
      name: Device Update Threshold
      description: How many devices need a pending update before updates are pushed.
      default: 1
      selector:
        number:
          min: 1
          max: 20
          step: 1
          mode: slider
    update_delay:
      name: Delay Before Updating
      description: How many minutes to wait before pushing updates.
      default: 30
      selector:
        number:
          min: 0
          max: 60
          step: 1
          mode: slider
          unit_of_measurement: minutes
    cooldown:
      name: Cooldown After Update
      description: Minutes to wait after updates complete before the automation can trigger again.
      default: 30
      selector:
        number:
          min: 0
          max: 120
          step: 1
          mode: slider
          unit_of_measurement: minutes
    time_window_start:
      name: Update Window Start
      description: Earliest time of day updates can be triggered.
      default: "08:00:00"
      selector:
        time:
    time_window_end:
      name: Update Window End
      description: Latest time of day updates can be triggered.
      default: "21:00:00"
      selector:
        time:
    notify_device:
      name: Notification Device
      description: Device to notify before updates are pushed.
      selector:
        device:
          integration: mobile_app
    update_mode:
      name: Update Mode
      description: Update all devices at once or one at a time sequentially.
      default: "all_at_once"
      selector:
        select:
          options:
            - label: "All at once"
              value: "all_at_once"
            - label: "One at a time (Sequential)"
              value: "sequential"
    sequential_delay:
      name: Delay Between Sequential Updates
      description: Minutes to wait between each device when using sequential mode.
      default: 0
      selector:
        number:
          min: 0
          max: 10
          step: 1
          mode: slider
          unit_of_measurement: minutes

variables:
  update_threshold: !input update_threshold
  update_delay: !input update_delay
  cooldown: !input cooldown
  update_mode: !input update_mode
  sequential_delay: !input sequential_delay
  device_list: >
    {{ expand(integration_entities('esphome'))
       | selectattr("entity_id", "contains", "update")
       | selectattr("state", "eq", "on")
       | map(attribute='entity_id') | list }}
  device_names: >
    {{ expand(integration_entities('esphome'))
       | selectattr("entity_id", "contains", "update")
       | selectattr("state", "eq", "on")
       | map(attribute='name') | join(', ') }}
  device_count: >
    {{ expand(integration_entities('esphome'))
       | selectattr("entity_id", "contains", "update")
       | selectattr("state", "eq", "on")
       | list | count }}
trigger:
  - platform: template
    value_template: >
      {{ expand(integration_entities('esphome'))
         | selectattr("entity_id", "contains", "update")
         | selectattr("state", "eq", "on")
         | list | count > update_threshold }}
  - platform: time
    at: !input time_window_start

condition:
  - condition: time
    after: !input time_window_start
    before: !input time_window_end
  - condition: template
    value_template: >
      {{ expand(integration_entities('esphome'))
         | selectattr("entity_id", "contains", "update")
         | selectattr("state", "eq", "on")
         | list | count > update_threshold }}
action:
  - domain: mobile_app
    type: notify
    device_id: !input notify_device
    title: "ESPHome Device Update(s)"
    message: >
      {{ device_count }} device(s) need updating: {{ device_names }}.
      Updates will begin in {{ update_delay }} minute(s).
    data:
      sticky: "true"
      persistent: "true"
      actions:
        - action: "UPDATE_NOW_ESPHOME"
          title: "Update Now"
        - action: "CANCEL_ESPHOME_UPDATE"
          title: "Cancel"

  - wait_for_trigger:
      - platform: event
        event_type: mobile_app_notification_action
        event_data:
          action: "CANCEL_ESPHOME_UPDATE"
      - platform: event
        event_type: mobile_app_notification_action
        event_data:
          action: "UPDATE_NOW_ESPHOME"
    timeout:
      minutes: "{{ update_delay }}"
    continue_on_timeout: true

  - if:
      - condition: template
        value_template: >
          {{ wait.trigger is not none and
             wait.trigger.event.data.action == 'CANCEL_ESPHOME_UPDATE' }}
    then:
      - domain: mobile_app
        type: notify
        device_id: !input notify_device
        title: "ESPHome Update Cancelled"
        message: "ESPHome device updates have been cancelled."
    else:
      - if:
          - condition: template
            value_template: "{{ update_mode == 'sequential' }}"
        then:
          - repeat:
              for_each: "{{ device_list }}"
              sequence:
                - action: update.install
                  continue_on_error: true
                  data: {}
                  target:
                    entity_id: "{{ repeat.item }}"
                - delay:
                    minutes: "{{ 0 if repeat.last else sequential_delay }}"
        else:
          - action: update.install
            continue_on_error: true
            data: {}
            target:
              entity_id: "{{ device_list }}"

      - domain: mobile_app
        type: notify
        device_id: !input notify_device
        title: "ESPHome Updates Complete"
        message: >
          {{ device_count }} device(s) have been updated: {{ device_names }}.
      - delay:
          minutes: "{{ cooldown }}"

mode: single


:light_bulb: Tips, Tricks and Gotchas

  • If you accidentally dismiss the notification without tapping a button, the automation will wait out the full delay and then proceed with updates automatically
  • On Android, if the action buttons are not visible try long pressing or expanding the notification
  • On iOS, action buttons require a long press on the notification to reveal them
  • Sequential mode is gentler on your network and the ESPHome add-on — recommended if you have many devices
  • If an update fails on one device in sequential mode, the automation will continue to the next device thanks to continue_on_error
  • Make sure your update window is wide enough to accommodate the delay + total update time for all your devices

:hammer: Changelog

  • 2026-03-20: Initial release
2 Likes

Hey is there a way to use the telegram bot as an notification device?
i like to send the information into a group.

You might need to update your script, having issues with undefined variables also it doesn't pickup user notification entities. Plus it errors on users phoned devices (most likely due to missing messaging integrations, so it should to Home Assistant Companion APP on the user's phonenotifications instead.

So I've set up the automation via blue print

Getting some errors:

Logger: homeassistant.components.template.trigger
Source: components/template/trigger.py:63
Integration: Template (documentation, issues)
First occurred: 2:17:42 PM (1 occurrence)
Last logged: 2:17:42 PM

1st error:
Error initializing 'template' trigger for 'ESPHome Device Auto Bulk Update': UndefinedError: 'update_threshold' is undefined

2nd error:
Logger: homeassistant.helpers.event
Source: helpers/template/init.py:360
First occurred: 2:17:42 PM (1 occurrence)
Last logged: 2:17:42 PM

Error while processing template: Template<template=({{ expand(integration_entities('esphome')) | selectattr("entity_id", "contains", "update") | selectattr("state", "eq", "on") | list | count > update_threshold }}) renders=5348>
Traceback (most recent call last):
File "/usr/src/homeassistant/homeassistant/helpers/template/init.py", line 358, in async_render
render_result = render_with_context(self.template, compiled, **kwargs)
File "/usr/src/homeassistant/homeassistant/helpers/template/context.py", line 45, in render_with_context
return template.render(**kwargs)

File "/usr/local/lib/python3.14/site-packages/jinja2/environment.py", line 1295, in render
self.environment.handle_exception()
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^
File "/usr/local/lib/python3.14/site-packages/jinja2/environment.py", line 942, in handle_exception
raise rewrite_traceback_stack(source=source)
File "", line 4, in top-level template code
jinja2.exceptions.UndefinedError: 'update_threshold' is undefined

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
File "/usr/src/homeassistant/homeassistant/helpers/template/**init**.py", line 476, in async_render_to_info
render_info._result = self.async_render( # noqa: SLF001
~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^
variables, strict=strict, log_fn=log_fn, **kwargs
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
)
^
File "/usr/src/homeassistant/homeassistant/helpers/template/**init**.py", line 360, in async_render
raise TemplateError(err) from err
homeassistant.exceptions.TemplateError: UndefinedError: 'update_threshold' is undefined

3rd error:
Logger: homeassistant.components.template.trigger
Source: components/template/trigger.py:79
Integration: Template (documentation, issues)
First occurred: 2:17:43 PM (1 occurrence)
Last logged: 2:17:43 PM

Error evaluating 'template' trigger for 'ESPHome Device Auto Bulk Update': UndefinedError: 'update_threshold' is undefined

This is most likely due to changes over time with Home Assistant.
Could you please review your blueprint with the latest HAOS, Core, Frontend
Thank you

:counterclockwise_arrows_button: Changelog

v1.1.0 — (May 11, 2026)

  • Fixed UndefinedError: 'update_threshold' is undefined in the template trigger — variables are not available in triggers in Home Assistant, threshold check moved to the condition block where it is properly resolved
  • Added continue_on_error: true to all notification steps so a missing or misconfigured Companion App will no longer cause the automation to fail entirely
  • Updated notification device input description to make it clearer that the Home Assistant Companion App is required
  • Condition threshold check corrected from > to >= for more accurate device counting

@lordwizzard

Thanks for the detailed feedback and for taking the time to test it! You're absolutely right on the trigger and template errors.

The update_threshold undefined error is a Home Assistant limitation I overlooked — variables defined in the variables: block are not available in triggers, only in conditions and actions. I've fixed this by changing the template trigger to use count > 0 instead, and moving the actual threshold comparison to the condition block where the variable is properly resolved.

I could not replicate the phone error you are describing, but I added continue_on_error: true to all notification steps so that users without the Home Assistant Companion App installed, or with misconfigured devices, won't have the entire automation fail on the notification step. I've also updated the notification device description in the blueprint to make it clearer that the Companion App is required. All notifications just go through to the notification app itself.

The updated blueprint is live on GitHub now — you can re-import it in Settings>Automations>Blueprints>ESPHome Device Auto Bulk Update - Overflow Menu (3dot menu on right side) and select Re-import blueprint, using the button at the top of the post, or grab it directly from the repo. Let me know if you run into any other issues!

@matze.lingen not without serious rework. You could fork the repo and give it a go if you like. The 3rd point below is a dealbreaker for me as I don't use telegram at all.

What would need to change:

  • The notification input — instead of selecting a device from the companion app, the user would enter their Telegram notify entity name (something like notify.telegram_bot_chat) as a text field in the blueprint
  • The notification action — instead of domain: mobile_app / type: notify it would use action: notify.send_message targeting the Telegram entity
  • The Cancel/Update Now buttons — this is the big problem. The companion app uses mobile_app_notification_action events to send button taps back to HA. Telegram uses a completely different system called inline keyboards and callback queries via telegram_callback events. The wait_for_trigger block would need to be rewritten entirely to listen for these instead
  • Sticky/persistent notification data — those are companion app only and would need to be removed

i see the problem, i just tested around a bit and will continue the next days. hope i find a solution.

I got the blueprint/automation running last night. I had a bad phone app with a issue. I deleted the HA app for Android and reinstalled it and the Companion App. HA found the new install from the phone. Got the message "No new updates for ESPHome" on the phone.

:pushpin: Update — June 2026

I've restructured my GitHub repo into a per-blueprint subdirectory layout. The import button in the original post is now pointing to the NEW path — if you are already using this blueprint please use the updated link (also below) to re-import and overwrite the original in your HA instance:

Import Blueprint