Bidirectional Shopping List Sync Automation

This automation synchronizes two to-do lists bidirectionally. I use it to ensure that any changes made to my Mealie shopping list are reflected in my Bring! shopping list and vice versa. Since the Bring! app is more practical for shopping, this setup helps keep everything up to date automatically.

At first, I thought syncing two to-do lists wouldn’t be too complicated—but wow, was I wrong. This automation took me hundreds of attempts, and I accidentally deleted and created list entries countless times. I’m not great with templates/Jinja, which might have contributed to the struggle, but since I couldn’t find much information on this topic, I decided to share my solution to hopefully save others some headaches.

:warning: Warning: Proceed with Caution When Automating To-Do Lists

Synchronizing to-do lists through automation can potentially disrupt your lists if not implemented carefully. While this solution works reliable in my specific setup, I’ve encountered numerous edge cases that make this process surprisingly complex.

Before Implementation:

  • Create two test lists to experiment with
  • Thoroughly test the automation in various scenarios
  • Verify behavior when lists are edited simultaneously, offline, or during sync processes

How it works:

  • The automation triggers two scripts that update the other shopping list whenever one of the lists changes.
  • As an extra safeguard, it also triggers a sync every 15 minutes, updating the lists based on which one was modified more recently.
  • Items are never deleted, only marked as done, to prevent accidental loss of entries.
  • A 5-second delay is included to slow things down a bit and allow the user to finish editing an entry before the sync kicks in.
  • This isn’t a perfect solution, but after countless attempts, I’m just happy it finally works! If you have suggestions for improvements, I’d love to hear them.

The automation which triggers the scripts for syncing:

alias: Mealie | Mealie & Bring | Sync
description: Synchronizes the shopping lists between Mealie and Bring! bidirectionally.
triggers:
  - entity_id: todo.mealie_wocheneinkauf
    trigger: state
    alias: Mealie list was updated
    id: Mealie list was updated
  - entity_id: todo.einkaufen
    trigger: state
    alias: Bring! list was updated
    id: Bring! list was updated
  - alias: 15-minute trigger
    trigger: time_pattern
    id: 15-minute trigger
    minutes: /15
actions:
  - delay:
      hours: 0
      minutes: 0
      seconds: 5
      milliseconds: 0
  - choose:
      - conditions:
          - condition: trigger
            id:
              - Mealie list was updated
        sequence:
          - action: script.mealie_todo_liste_synchronisieren_mealie_liste
            metadata: {}
            data: {}
            alias: Update Mealie list
        alias: Mealie list was updated (update Bring!)
      - conditions:
          - condition: trigger
            id:
              - Bring! list was updated
        sequence:
          - action: script.mealie_todo_liste_synchronisieren_bring_liste
            metadata: {}
            data: {}
            alias: Update Bring! list
        alias: Bring! list was updated (update Mealie)
      - conditions:
          - condition: trigger
            id:
              - 15-minute trigger
        sequence:
          - alias: Update lists based on most recent changes
            if:
              - condition: template
                value_template: >-
                  {{ states.todo.einkaufen.last_changed >
                  states.todo.mealie_wocheneinkauf.last_changed }}
            then:
              - sequence:
                  - action: script.mealie_todo_liste_synchronisieren_bring_liste
                    metadata: {}
                    data: {}
                    alias: Update Bring! list
                  - action: script.mealie_todo_liste_synchronisieren_mealie_liste
                    metadata: {}
                    data: {}
                    alias: Update Mealie list
            else:
              - sequence:
                  - action: script.mealie_todo_liste_synchronisieren_mealie_liste
                    metadata: {}
                    data: {}
                    alias: Update Mealie list
                  - action: script.mealie_todo_liste_synchronisieren_bring_liste
                    metadata: {}
                    data: {}
                    alias: Update Bring! list
mode: single

Script: Update Bring! List

sequence:
  - alias: Update list | Bring!
    sequence:
      - alias: Retrieve existing entries from Bring! list
        target:
          entity_id: todo.einkaufen
        data:
          status:
            - needs_action
            - completed
        response_variable: source_items
        action: todo.get_items
      - alias: Retrieve existing entries from Mealie list
        target:
          entity_id: todo.mealie_wocheneinkauf
        data:
          status:
            - needs_action
            - completed
        response_variable: existing_items
        action: todo.get_items
      - alias: Add new entries to Mealie list
        repeat:
          for_each: |-
            {{
              source_items['todo.einkaufen']['items']
              | selectattr('status', 'eq', 'needs_action')
              | rejectattr('summary', 'in', existing_items['todo.mealie_wocheneinkauf']['items'] | map(attribute='summary') | list)
              | list
            }}
          sequence:
            - target:
                entity_id: todo.mealie_wocheneinkauf
              data:
                item: "{{ repeat.item.summary }}"
              action: todo.add_item
      - alias: Mark completed items as done in Mealie
        repeat:
          for_each: |-
            {{
              source_items['todo.einkaufen']['items']
              | selectattr('status', 'eq', 'completed')
              | selectattr('summary', 'in', existing_items['todo.mealie_wocheneinkauf']['items'] | selectattr('status', 'ne', 'completed') | map(attribute='summary') | list)
              | list
            }}
          sequence:
            - target:
                entity_id: todo.mealie_wocheneinkauf
              data:
                item: "{{ repeat.item.summary }}"
                status: completed
              action: todo.update_item
      - alias: Reset re-added items
        repeat:
          for_each: |-
            {{
              source_items['todo.einkaufen']['items']
              | selectattr('status', 'eq', 'needs_action')
              | selectattr('summary', 'in', existing_items['todo.mealie_wocheneinkauf']['items'] | selectattr('status', 'eq', 'completed') | map(attribute='summary') | list)
              | list
            }}
          sequence:
            - target:
                entity_id: todo.mealie_wocheneinkauf
              data:
                item: "{{ repeat.item.summary }}"
                status: needs_action
              action: todo.update_item
alias: Mealie | Sync To-Do List | Bring! List
description: ""
icon: mdi:food-fork-drink

Script: Update Mealie List

sequence:
  - alias: Update list | Mealie
    sequence:
      - alias: Retrieve existing entries from Mealie list
        target:
          entity_id: todo.mealie_wocheneinkauf
        data:
          status:
            - needs_action
            - completed
        response_variable: source_items
        action: todo.get_items
      - alias: Retrieve existing entries from Bring! list
        target:
          entity_id: todo.einkaufen
        data:
          status:
            - needs_action
            - completed
        response_variable: existing_items
        action: todo.get_items
      - alias: Add new entries to Bring! list
        repeat:
          for_each: |-
            {{
              source_items['todo.mealie_wocheneinkauf']['items']
              | selectattr('status', 'eq', 'needs_action')
              | rejectattr('summary', 'in', existing_items['todo.einkaufen']['items'] | map(attribute='summary') | list)
              | list
            }}
          sequence:
            - target:
                entity_id: todo.einkaufen
              data:
                item: "{{ repeat.item.summary }}"
              action: todo.add_item
      - alias: Mark completed items as done in Bring!
        repeat:
          for_each: |-
            {{
              source_items['todo.mealie_wocheneinkauf']['items']
              | selectattr('status', 'eq', 'completed')
              | selectattr('summary', 'in', existing_items['todo.einkaufen']['items'] | selectattr('status', 'ne', 'completed') | map(attribute='summary') | list)
              | list
            }}
          sequence:
            - target:
                entity_id: todo.einkaufen
              data:
                item: "{{ repeat.item.summary }}"
                status: completed
              action: todo.update_item
      - alias: Reset re-added items
        repeat:
          for_each: |-
            {{
              source_items['todo.mealie_wocheneinkauf']['items']
              | selectattr('status', 'eq', 'needs_action')
              | selectattr('summary', 'in', existing_items['todo.einkaufen']['items'] | selectattr('status', 'eq', 'completed') | map(attribute='summary') | list)
              | list
            }}
          sequence:
            - target:
                entity_id: todo.einkaufen
              data:
                item: "{{ repeat.item.summary }}"
                status: needs_action
              action: todo.update_item
alias: Mealie | Sync To-Do List | Mealie List
description: ""
icon: mdi:food-fork-drink
4 Likes

Have you tested what happens if two people are operating on the lists at the same time?

Good catch! I haven’t tested what happens if the lists are edited simultaneously.

In theory, with the current setup, the automation waits 5 seconds before updating the other list based on the most recent change. However, if both lists are being edited at exactly the same time, this could lead to unexpected results. To prevent potential conflicts, it might be a good idea to add a safeguard.

One possible solution is to introduce a condition in the update script. This condition would ensure that the list being updated hasn’t been modified for a certain period—perhaps a few minutes. For example:

value_template: >-  
  {{ (now() - states.todo.mealie_wocheneinkauf.last_changed).total_seconds() > 180 }}  

What do you think?

Bidirectional Shopping List Sync Automation | V2

I’ve developed an improved version of my shopping list synchronization automation, specifically addressing the scenario where both lists are edited simultaneously.

Changes

  • Replaced state-based triggers with a 5-minute time-based check interval
  • Implemented a “freshness” evaluation system that:
    • Identifies which list was most recently modified
    • Only updates a list when it has not been edited for at least 3 minutes
  • Added reliability safeguards to prevent synchronization attempts when either list is unavailable (particularly important for the cloud-connected list)

Technical Approach

The automation now performs a comprehensive evaluation every 5 minutes:

  1. Compares the last-modified timestamps of both lists
  2. Verifies the more recent list has remained unchanged for 3+ minutes (ensuring edits are complete)
  3. Confirms both lists are accessible before initiating any updates
  4. Synchronizes by updating the older list with the contents of the newer list

The updated automation:

alias: Mealie | Mealie & Bring | Sync
description: Synchronizes shopping lists between Mealie and Bring! bidirectionally.
triggers:
  - alias: 5 Minute Trigger
    trigger: time_pattern
    id: 5 Minuten Trigger
    minutes: /5
conditions:
  - alias: Both Lists Are Available
    condition: not
    conditions:
      - condition: state
        entity_id: todo.einkaufen
        state: unavailable
      - condition: state
        entity_id: todo.einkaufen
        state: unknown
      - condition: state
        entity_id: todo.mealie_wocheneinkauf
        state: unavailable
      - condition: state
        entity_id: todo.mealie_wocheneinkauf
        state: unknown
actions:
  - choose:
      - conditions:
          - condition: template
            value_template: >-
              {{ states.todo.einkaufen.last_changed >
              states.todo.mealie_wocheneinkauf.last_changed }}
            alias: Bring! List is More Recent Than Mealie List
          - condition: template
            value_template: " {{ (now() - states.todo.mealie_wocheneinkauf.last_changed).total_seconds() > 180 }}"
            alias: Mealie List Has Not Been Changed For Several Minutes
        sequence:
          - sequence:
              - action: script.mealie_todo_liste_synchronisieren_bring_liste
                metadata: {}
                data: {}
                alias: Update Bring! List
              - action: script.mealie_todo_liste_synchronisieren_mealie_liste
                metadata: {}
                data: {}
                alias: Update Mealie List
        alias: Bring! List is More Recent Than Mealie List
      - conditions:
          - alias: Mealie List is More Recent Than Bring! List
            condition: template
            value_template: >-
              {{ states.todo.mealie_wocheneinkauf.last_changed >
              states.todo.einkaufen.last_changed }}
          - alias: Bring! List Has Not Been Changed For Several Minutes
            condition: template
            value_template: " {{ (now() - states.todo.einkaufen.last_changed).total_seconds() > 180 }}"
        sequence:
          - sequence:
              - action: script.mealie_todo_liste_synchronisieren_mealie_liste
                metadata: {}
                data: {}
                alias: Update Mealie List
              - action: script.mealie_todo_liste_synchronisieren_bring_liste
                metadata: {}
                data: {}
                alias: Update Bring! List
        alias: Mealie List is More Recent Than Bring! List
mode: single

This implementation has been functioning reliably in my testing so far. The logic should ensure that the most recent edits always take precedence, regardless of which list they were made on, while preventing synchronization conflicts.

Would appreciate any feedback on potential edge cases I might have overlooked!

howdy, i brought it up in the reddit thread but im still getting the odd duplication issues i mentioned there.

Hi there :slight_smile: I actually managed to replicate your issue now, but I still haven’t managed to include a more reliable filtering into the template.

For some mysterious reason some specific words are making problems and I have absolutely no idea what could cause this. In my case the word “Minze” is acting strange and causing errors. It’s such a weird error that I’m running out of ideas honestly. But I’m working on it!

1 Like

weird, maybe a check to see if item already exists on list? or a merger of the 2 lists then output unique only? i haven’t learned the ha scripting language at all but i feel like its doable?

I thought the exact same thing a few weeks back - ‘How hard could this be?’ I’m pretty new to Jinja scripting myself and was optimistic. Fast forward to 30+ hours of banging my head against the wall, trying literally hundreds of different approaches… and yeah, it’s WAY more complicated than it looks. Sure, it’s doable, but nailing all those edge cases when syncing lists? That’s some serious Jinja wizardry I just don’t have in my toolkit yet.

The weird part? My original automation has been working flawlessly with my lists and still does, even after I threw a bunch of random entries at it to stress-test. I’m gonna shelf this for a bit and come back when my brain recovers. Would seriously love if someone with more Jinja chops could jump in!

1 Like

Bidirectional Todo-List Sync V3 | The Sync Continues

Here we go again—now that my brain has (mostly) recovered, let’s give this another shot! This time, I’m taking a completely different approach. It may not be the most elegant solution, but it should help avoid issues with incrementing and duplicating list items.

The new logic simply purges the older list entirely and rebuilds all items according to their current state.


:warning: Warning

Before using this automation on your actual lists, test it first with two demo lists. While I believe I’ve accounted for all edge cases (hopefully), I’ve run into so many unexpected issues that I don’t fully trust it yet.


What if two people edit the lists at the exact same time?

If there’s a chance that both lists could be updated simultaneously in your setup, this approach won’t work for you. Since real-time syncing has been removed along with state-based triggers, changes will only sync at the next scheduled run.


Some observations

Whitespace issue on mobile

  • Sometimes, a blank space gets added at the end of entries—especially on mobile (some keyboards automatically add the space). The item is then saved with the space, but after iterating over it with a template, the space is removed, causing inconsistencies. Based on the solution from this thread (which i wished i had seen sooner) I attempted to work around this by using:
"{{ dict(item=repeat.item) }}"

Handling identical list items

  • Todo list actions require exact names to edit/delete items, but they also provide a unique ID (UID) (also wished i figured this out sooner).
  • I included this UID in the filter, which should prevent issues when handling duplicate items with the same name.

Changes in V3:

Time-based trigger only

  • The automation no longer relies on state changes of the lists to trigger.
  • Instead, it runs on a scheduled time-based trigger. If this new automation approach proves reliable, I may consider reintroducing the state-change trigger later.

Full list purge & rebuild

  • The automation removes all items from the older list.
  • It then copies everything from the more recent list.
  • It also syncs ALL the completed entries (with a probably not so efficient technique), which slows it down and may not be actually needed in your case. If you just care about the sync of unfinished tasks, it’s probably good to remove this part.

Ensuring the “fresher” list wins

  • To determine which list is fresher, the automation checks:
    • Last modified timestamp of each list.
    • If the list has been inactive for a set amount of time (to avoid syncing a partially edited list).

The new automation approach:

alias: Mealie & Bring | Bidirectional Todo-List Sync
description: Synchronizes shopping lists between Mealie and Bring! bidirectionally.
triggers:
  - alias: Trigger every 20 minutes
    trigger: time_pattern
    id: 20-Minute Trigger
    minutes: /20
conditions:
  - alias: Both lists are available
    condition: not
    conditions:
      - condition: state
        entity_id: todo.bring
        state: unavailable
      - condition: state
        entity_id: todo.bring
        state: unknown
      - condition: state
        entity_id: todo.mealie
        state: unavailable
      - condition: state
        entity_id: todo.mealie
        state: unknown
actions:
  - choose:
      - conditions:
          - condition: template
            value_template: >-
              {{ states.todo.bring.last_changed >
              states.todo.mealie.last_changed }}
            alias: Bring! list is more up-to-date than Mealie list
          - condition: template
            value_template: " {{ (now() - states.todo.mealie.last_changed).total_seconds() > 180 }}"
            alias: Mealie list has not been updated for several minutes
        sequence:
          - sequence:
              - alias: Clear the Mealie list
                sequence:
                  - alias: Retrieve existing entries from Mealie
                    target:
                      entity_id: todo.mealie
                    data:
                      status:
                        - needs_action
                        - completed
                    response_variable: mealie_items
                    action: todo.get_items
                  - alias: Empty the Mealie list
                    repeat:
                      while:
                        - condition: template
                          value_template: "{{ (states('todo.mealie') | int ) > 0 }}"
                      sequence:
                        - alias: Refresh Mealie list after deletion
                          target:
                            entity_id: todo.mealie
                          data:
                            status:
                              - needs_action
                              - completed
                          response_variable: mealie_items
                          action: todo.get_items
                        - delay: "00:00:02"
                        - repeat:
                            for_each: >-
                              {{ mealie_items['todo.mealie']['items'] |
                              map(attribute='summary') | list }}
                            sequence:
                              - target:
                                  entity_id: todo.mealie
                                data: |
                                  {{ dict(item=repeat.item) }}
                                action: todo.remove_item
              - alias: Update Mealie list with Bring! items
                sequence:
                  - alias: Retrieve all entries from Bring!
                    target:
                      entity_id: todo.bring
                    data:
                      status:
                        - needs_action
                        - completed
                    response_variable: bring_items
                    action: todo.get_items
                  - alias: Define variables for unique items
                    variables:
                      unique_needs_action_items: |-
                        {{ bring_items['todo.bring']['items'] 
                          | selectattr('status', 'eq', 'needs_action') 
                          | map(attribute='uid') 
                          | list 
                          | map('regex_replace', '^', '{\"uid\": \"') 
                          | map('regex_replace', '$', '\"}') 
                          | map('from_json') 
                          | list }}
                      unique_completed_items: |-
                        {{ bring_items['todo.bring']['items'] 
                          | selectattr('status', 'eq', 'completed') 
                          | map(attribute='uid') 
                          | list 
                          | map('regex_replace', '^', '{\"uid\": \"') 
                          | map('regex_replace', '$', '\"}') 
                          | map('from_json') 
                          | list }}
                  - alias: Copy pending items from Bring! to Mealie
                    repeat:
                      for_each: "{{ unique_needs_action_items }}"
                      sequence:
                        - target:
                            entity_id: todo.mealie
                          data:
                            item: >-
                              {{ bring_items['todo.bring']['items'] |
                              selectattr('uid', 'eq', repeat.item.uid) |
                              map(attribute='summary') | first }}
                          action: todo.add_item
                  - alias: >-
                      Copy completed items from Bring! to Mealie and mark as
                      completed
                    repeat:
                      for_each: "{{ unique_completed_items }}"
                      sequence:
                        - target:
                            entity_id: todo.mealie
                          data:
                            item: >-
                              {{ bring_items['todo.bring']['items'] |
                              selectattr('uid', 'eq', repeat.item.uid) |
                              map(attribute='summary') | first }}
                          action: todo.add_item
                        - target:
                            entity_id: todo.mealie
                          data:
                            item: >-
                              {{ bring_items['todo.bring']['items'] |
                              selectattr('uid', 'eq', repeat.item.uid) |
                              map(attribute='summary') | first }}
                            status: completed
                          action: todo.update_item
            alias: Update Mealie list
        alias: Bring! list is more up-to-date than Mealie list
      - conditions:
          - alias: Mealie list is more up-to-date than Bring! list
            condition: template
            value_template: >-
              {{ states.todo.mealie.last_changed >
              states.todo.bring.last_changed }}
          - alias: Bring! list has not been updated for several minutes
            condition: template
            value_template: " {{ (now() - states.todo.bring.last_changed).total_seconds() > 180 }}"
        sequence:
          - alias: Update Bring! list with Mealie items
            sequence:
              - alias: Clear the Bring! list
                sequence:
                  - alias: Retrieve existing entries from Bring!
                    target:
                      entity_id: todo.bring
                    data:
                      status:
                        - needs_action
                        - completed
                    response_variable: bring_items
                    action: todo.get_items
                  - alias: Empty the Bring! list
                    repeat:
                      while:
                        - condition: template
                          value_template: "{{ (states('todo.bring') | int ) > 0 }}"
                      sequence:
                        - alias: Refresh Bring! list after deletion
                          target:
                            entity_id: todo.bring
                          data:
                            status:
                              - needs_action
                              - completed
                          response_variable: bring_items
                          action: todo.get_items
                        - delay: "00:00:02"
                        - repeat:
                            for_each: >-
                              {{ bring_items['todo.bring']['items'] |
                              map(attribute='summary') | list }}
                            sequence:
                              - target:
                                  entity_id: todo.bring
                                data: |
                                  {{ dict(item=repeat.item) }}
                                action: todo.remove_item
              - alias: Update Bring! list with Mealie items
                sequence:
                  - alias: Retrieve all entries from Mealie
                    target:
                      entity_id: todo.mealie
                    data:
                      status:
                        - needs_action
                        - completed
                    response_variable: mealie_items
                    action: todo.get_items
                  - alias: Define variables for unique items
                    variables:
                      unique_needs_action_items: |-
                        {{ mealie_items['todo.mealie']['items'] 
                          | selectattr('status', 'eq', 'needs_action') 
                          | map(attribute='uid') 
                          | list 
                          | map('regex_replace', '^', '{\"uid\": \"') 
                          | map('regex_replace', '$', '\"}') 
                          | map('from_json') 
                          | list }}
                      unique_completed_items: |-
                        {{ mealie_items['todo.mealie']['items'] 
                          | selectattr('status', 'eq', 'completed') 
                          | map(attribute='uid') 
                          | list 
                          | map('regex_replace', '^', '{\"uid\": \"') 
                          | map('regex_replace', '$', '\"}') 
                          | map('from_json') 
                          | list }}
                  - alias: Copy pending items from Mealie to Bring!
                    repeat:
                      for_each: "{{ unique_needs_action_items }}"
                      sequence:
                        - target:
                            entity_id: todo.bring
                          data:
                            item: >-
                              {{ mealie_items['todo.mealie']['items'] |
                              selectattr('uid', 'eq', repeat.item.uid) |
                              map(attribute='summary') | first }}
                          action: todo.add_item
                  - alias: >-
                      Copy completed items from Mealie to Bring! and mark as
                      completed
                    repeat:
                      for_each: "{{ unique_completed_items }}"
                      sequence:
                        - target:
                            entity_id: todo.bring
                          data:
                            item: >-
                              {{ mealie_items['todo.mealie']['items'] |
                              selectattr('uid', 'eq', repeat.item.uid) |
                              map(attribute='summary') | first }}
                          action: todo.add_item
                        - target:
                            entity_id: todo.bring
                          data:
                            item: >-
                              {{ mealie_items['todo.mealie']['items'] |
                              selectattr('uid', 'eq', repeat.item.uid) |
                              map(attribute='summary') | first }}
                            status: completed
                          action: todo.update_item
        alias: Mealie list is more up-to-date than Bring! list
    enabled: true
mode: single
1 Like

awesome! ill try this out tomorrow.

I need to do some more testing, but for now this seems to be working great.

Also, a big thank you for sharing and further fine-tuning your automation! :slightly_smiling_face:

1 Like

Minor Update: Preventing Sync of Identical Lists

I’ve created an additional check for the automation that compares both lists and stops the sync if they match. This prevents unnecessary purging and rebuilding of the lists, ensuring the to-do list’s ‘last edited’ timestamp actually reflects real changes.

The snippet can be added as an optional first step in the automation.

alias: Check if lists are different
sequence:
  - action: todo.get_items
    data:
      status:
        - needs_action
        - completed
    response_variable: mealie_all_items
    target:
      entity_id: todo.mealie_wocheneinkauf
    alias: Get All Mealie list entries
  - alias: Get All Bring! list entries
    action: todo.get_items
    data:
      status:
        - needs_action
        - completed
    response_variable: bring_all_items
    target:
      entity_id: todo.einkaufen
  - alias: Check If both lists are exactly the same
    if:
      - condition: template
        value_template: >-
          {% set mealie_items = mealie_all_items | map(attribute='summary') |
          list | sort %} {% set bring_items = bring_all_items |
          map(attribute='summary') | list | sort %} {{ (mealie_items |
          default([])) == (bring_items | default([])) }}
    then:
      - stop: Both lists are identical.

only issue here sofar is the sync will overwrite the google keep based list if it hasn’t synced down yet. so i think ill try to get that keep list to sync faster.

no duplication yet though

seems to have abug here,

{% set mealie_all_items = {"todo.mealie_mealie":{"items":[{"summary":"Balloons","uid":"54aa520d-0852-4729-a5b4-c05ab0fc48cf","status":"needs_action"},{"summary":"Coffee","uid":"cfc3a53a-86d2-4c2d-9176-75e9f06b4503","status":"needs_action"},{"summary":"Cupcakes","uid":"133e817c-e38f-40e2-b8c9-af1766db659c","status":"needs_action"},{"summary":"Night time diapers","uid":"1fb1dff4-0739-4db4-b0f0-f2a70bad4084","status":"needs_action"},{"summary":"Olives","uid":"a1c699d3-c25f-4403-8d10-2654bd10f181","status":"needs_action"},{"summary":"Tylenol 650mg","uid":"bd4c8f26-357c-44be-beb4-e8c7681a8c3f","status":"needs_action"}]}} %}
{% set bring_all_items = {"todo.google_keep_groceries":{"items":[{"summary":"2 liter sodas","uid":"1955e18a71a.089c3013c013471e","status":"completed"},{"summary":"Balloons","uid":"1955e1878aa.105cfb16ee2e296f","status":"completed"},{"summary":"Coffee","uid":"1955e187ff7.f104ce9aff11937f","status":"needs_action"},{"summary":"Creamer","uid":"cbx.a1ob4h1ychpl","status":"needs_action"},{"summary":"Cupcakes","uid":"1955e1887df.17db18d34ad98bca","status":"completed"},{"summary":"Litterbox","uid":"cbx.pm40pbr9jzkv","status":"needs_action"},{"summary":"Milk","uid":"cbx.lieil9m291c2","status":"needs_action"},{"summary":"Night time diapers","uid":"1955e188f7d.05ece5c6f0878380","status":"needs_action"},{"summary":"Olives","uid":"1955e189366.3c307c573ec006e8","status":"needs_action"},{"summary":"Tylenol 650mg","uid":"1955e189f24.671d5875a9154fdd","status":"needs_action"}]}} %}
{% set mealie_items = mealie_all_items | map(attribute='summary')
              | list | sort %} {% set bring_items = bring_all_items |
              map(attribute='summary') | list | sort %} {{ (mealie_items |
              default([])) == (bring_items | default([])) }}

the items created from all items are undefined, cant seem to get the sub items?

{% set mealie_all_items = {"todo.mealie_mealie":{"items":[{"summary":"Balloons","uid":"54aa520d-0852-4729-a5b4-c05ab0fc48cf","status":"needs_action"},{"summary":"Coffee","uid":"cfc3a53a-86d2-4c2d-9176-75e9f06b4503","status":"needs_action"},{"summary":"Cupcakes","uid":"133e817c-e38f-40e2-b8c9-af1766db659c","status":"needs_action"},{"summary":"Night time diapers","uid":"1fb1dff4-0739-4db4-b0f0-f2a70bad4084","status":"needs_action"},{"summary":"Olives","uid":"a1c699d3-c25f-4403-8d10-2654bd10f181","status":"needs_action"},{"summary":"Tylenol 650mg","uid":"bd4c8f26-357c-44be-beb4-e8c7681a8c3f","status":"needs_action"}]}} %}
{% set bring_all_items = {"todo.google_keep_groceries":{"items":[{"summary":"2 liter sodas","uid":"1955e18a71a.089c3013c013471e","status":"completed"},{"summary":"Balloons","uid":"1955e1878aa.105cfb16ee2e296f","status":"completed"},{"summary":"Coffee","uid":"1955e187ff7.f104ce9aff11937f","status":"needs_action"},{"summary":"Creamer","uid":"cbx.a1ob4h1ychpl","status":"needs_action"},{"summary":"Cupcakes","uid":"1955e1887df.17db18d34ad98bca","status":"completed"},{"summary":"Litterbox","uid":"cbx.pm40pbr9jzkv","status":"needs_action"},{"summary":"Milk","uid":"cbx.lieil9m291c2","status":"needs_action"},{"summary":"Night time diapers","uid":"1955e188f7d.05ece5c6f0878380","status":"needs_action"},{"summary":"Olives","uid":"1955e189366.3c307c573ec006e8","status":"needs_action"},{"summary":"Tylenol 650mg","uid":"1955e189f24.671d5875a9154fdd","status":"needs_action"}]}} %}
{% set mealie_items = mealie_all_items['todo.mealie_mealie']['items'] | map(attribute='summary')
              | list | sort %} {% set bring_items = bring_all_items['todo.google_keep_groceries']['items'] |
              map(attribute='summary') | list | sort %} {{ (mealie_items |
              default([])) == (bring_items | default([])) }}

seems to work as intended

Thanks for pointing out this bug! I also realized that the template logic needed some fine-tuning. Previously, it was only comparing item names, but we also need to check the list item state. This way, if an item changes its state (e.g., from "needs_action" to "completed"), the lists will no longer be considered identical.

I gave it another try, and now the template:

  • Retrieves all items from both lists in a single action.
  • Extracts only the items with the state "needs_action".
  • Compares both lists and returns true only if they are identical (same items and same states).
  • This template intentionally ignores changes to "completed" items— it will still return true if only the "completed" items differ.
  • Note: This template also ignores descriptions since one of my lists does not support them. If you need to track description changes, you’ll need to adjust the code accordingly.
alias: Check if lists are different
sequence:
  - alias: Get ALL list entries of both lists
    action: todo.get_items
    metadata: {}
    data:
      status:
        - needs_action
        - completed
    response_variable: all_items
    target:
      entity_id:
        - todo.mealie_wocheneinkauf
        - todo.einkaufen
  - alias: Check If both lists are identical
    if:
      - condition: template
        value_template: >
          {% set einkaufen_items = all_items['todo.einkaufen']['items'] |
          selectattr('status', 'equalto', 'needs_action') | map(attribute='summary') | list | sort %}
          
          {% set mealie_items = all_items['todo.mealie_wocheneinkauf']['items'] | 
          selectattr('status', 'equalto', 'needs_action') | map(attribute='summary') | list | sort %}

          {{ einkaufen_items == mealie_items }}
    then:
      - stop: Both lists are identical.
enabled: true

This approach seems to be working well in my setup so far, but I’d appreciate your feedback on whether it works with your lists too.

I encountered that this morning too, didnt sit down to fix it, ill add this too . You think youll publish this as a template or in hacs?

ok! looks good sofar. ill let it soak over night

You mean as a blueprint? I don’t think so. The automation works fine for now, but I’m not exactly happy with it—though I also have zero motivation to fine-tune it any further. Honestly, I kind of regret even trying to automate this in the first place :joy:

Im glad you did, better you than me. Thanks again!

1 Like