Mealie in HA

i wanted to use the docker version but didnt manage to get it work on the webpage card. so i use the add on. when installed searh for https://YOUR_IP:9000/

Hi, thank you for this write-up! Been really handy. I think it may be outdated now as when I try to copy the code for the week menu I get this error: Invalid config for 'rest' from integration 'sensor' at configuration.yaml, line 167: 'sensor' is an invalid option for 'sensor.rest', check: sensor

I canā€™t seem to resolve it.

Hi @Jakesa, I double checked the code and itā€™s correct. I run HA version 2024.3.0
Do you have defined it in configuration.yaml under the root or under/in a sensor.yaml file?

Sorry, my bad. Iā€™m running 2024.3.1 I had it defined under the root. Are yours in a sensor.yaml file?

EDIT: Scrap that. I got it sorted. My config was a bit messed up.

Hey all. Not sure if this is useful for anyone, but I thought Iā€™d share it anyway.

We use and love Mealie in my house, but when we are planning out our grocery list for the week, it usually involves looking up some recipes in Mealie, and manually adding their ingredients to our shopping list in Todoist, either via the Todoist app or by asking Alexa to add them.

I decided to write an automation to improve this a bit. Essentially now I can add a Mealie recipe to a Mealie shopping list, all in the Mealie web app. Home Assistant will then read those ingredients which were added, and one by one transfer them to my Todoist groceries list, deleting them from Mealie after it has done each one. This saves me manually typing or announcing each ingredient to Alexa.

UPDATED SOLUTION AUGUST 2024:
Home Assistant 2024.7 introduced an official Mealie integration, so Iā€™d recommend using that to get the data into Home Assistant rather than my custom rest sensor based solution below. :slight_smile: Original solution kept below for posterity.

ORIGINAL SOLUTION FROM MARCH 2024:
If youā€™d like to use it yourself, you just need to do the following two things:

  1. Add two new secrets to your secrets.yaml file and replace the values as appropriate

    • mealie_shopping_items_api_endpoint: https://[my-mealie-url]/api/groups/shopping/items
      being sure to repace my-mealie-url with your mealie host name or IP address
    • mealie_api_token: Bearer xxxxxxxxxx
      being sure to replace xxxxxxxxxx with your Mealie API token (see this page for details).
  2. Change the two variables commented with #CHANGEME

    • target_shopping_list can be any list available in Home Assistant, doesnā€™t have to be a Todoist list
    • notify_service is what I use to send me a notification to my phone if it fails to delete an item from Mealie for whatever reason, just in case

Hereā€™s the YAML:

sensor:
  #Sensor to view items which are on any Mealie shopping list
  - platform: rest
    name: Mealie Shopping List Items
    resource_template: !secret mealie_shopping_items_api_endpoint
    headers:
      Authorization: !secret mealie_api_token
    params:
      perPage: 1
      queryFilter: checked=false
      orderBy: food.name
      orderDirection: asc

    #As long as the request contains "total", the API was successfully reached
    availability: >
      {{ value_json['total'] }}

    #Extract the ingredient data in the following format: "food - quantity unit" eg. "apples - 1.5 kilogram", or "Empty List" if no items are left in the list
    value_template: >
      {# If no items in list, return "Empty List" #}
      {% if value_json['total'] == 0 %}
        Empty List

      {# If distinct food is not defined use display name instead #}
      {% elif value_json['items'][0]['food'] == none %}
        {{ value_json['items'][0]['display'] }}

      {# If quantity is available, include it in the output #}
      {% elif value_json['items'][0]['quantity'] > 0 %}
        {{ value_json['items'][0]['food']['name'] }} - {{ (value_json['items'][0]['quantity'] | round(3) | string).rstrip('0').rstrip('.') }} {{ value_json['items'][0]['unit']['name'] }}

      {# Otherwise, print just the food name #}
      {% else %}
        {{ value_json['items'][0]['food']['name'] }}
        
      {% endif %}

    #Extract the ingredient's ID. Required to delete through the API later on.
    json_attributes_path: "$.items.0"
    json_attributes:
      - id

rest_command:
  #Command to delete a Mealing shopping list item
  mealie_delete_from_shopping_list:
    url: >
      {{ url }}/{{ state_attr('sensor.mealie_shopping_list_items', 'id') }}
    method: delete
    headers:
      Authorization:  !secret mealie_api_token

automation:
  #Automation to regularly refresh the "Mealie Shopping List Items" sensor 
  - id: mealie_update_rest_sensors
    alias: "Mealie - Update Rest Sensors"
    initial_state: "on"
    trigger:
      - platform: time_pattern
        seconds: "/10" #How often do you want to check for updates in Mealie?
    action:
      - service: homeassistant.update_entity
        data:
          entity_id:
            - sensor.mealie_shopping_list_items

  #Automation to transfer items from the Mealie shopping list to another list of your choice
  - id: mealie_transfer_shopping_list
    alias: "Mealie - Transfer Shopping List"
    description: "When shopping list items are added to the Mealie shopping list, transfer them to an alternative list."
    mode: queued
    max: 10
    initial_state: "on"
    variables:
      target_shopping_list: todo.groceries_list #CHANGEME: entity ID of your shopping list
      notify_service: notify.mobile_app_michael_s_galaxy_s10 #CHANGEME: where to be notified if items fail to delete from Mealie shopping list
    trigger:
      - id: "regular_trigger"
        platform: state
        entity_id: sensor.mealie_shopping_list_items
      - id: "fallback_trigger"
        platform: time_pattern
        minutes: "/10"
    condition:
      - condition: not
        conditions:
          - condition: state
            entity_id: sensor.mealie_shopping_list_items
            state: "Empty List"
    action:
      - alias: "Ensure shopping list item isn't now 'Empty List'"
        condition: not
        conditions:
          - condition: state
            entity_id: sensor.mealie_shopping_list_items
            state: "Empty List"

      - alias: "Add shopping list item to target list"
        service: todo.add_item
        target:
          entity_id: "{{ target_shopping_list }}"
        data:
          item: "{{ states('sensor.mealie_shopping_list_items') }}"
          description: Imported From Mealie

      - alias: "Delete shopping list item from Mealie"
        service: rest_command.mealie_delete_from_shopping_list
        data:
          url: !secret mealie_shopping_items_api_endpoint
        response_variable: mealie_delete_response

      - alias: "Notify if item failed to delete from Mealie"
        if: "{{ mealie_delete_response['status'] != 200 }}"
        then:
          - service: "{{ notify_service }}"
            data:
              title: "Failed to delete item from Mealie list"
              message: "{{ mealie_delete_response['content'] }}"
              data:
                notification_icon: "mdi:food-off"
                
      - alias: "Refresh Mealie shopping list items sensor, in case more items need processing"
        service: homeassistant.update_entity
        data:
          entity_id:
            - sensor.mealie_shopping_list_items

And hereā€™s what to add to your secret.yaml:

mealie_shopping_items_api_endpoint: https://[my-mealie-url]/api/groups/shopping/items
mealie_api_token: Bearer xxxxxxxxxx

If I get the time one day I might look at trying to turn this into a blueprint or a HACS integration, but hopefully this is useful for some people in the mean time!

Edit: Updated the template sensor to handle recipes where the ingredients are added as just text and not linked to specific foods.

8 Likes

I am trying to copy the markdown card. I have all the entities exposed needed for it:

Screenshot 2024-03-23 at 16.45.17

But using this Markdown code:

type: markdown
content: |-
  <table> 
         {% for i in range(7) %}
             {% set index = i|string %}
             {% set meal_date = states("sensor.mealie_day" + index + "_date") %}
             {% set meal_name = states("sensor.mealie_day" + index + "_name") %}
             {% if meal_date != 'unknown' %} 
             <tr>
                 <td>
                  {{ ['ma','di','wo','do','vr','za','zo'][strptime(meal_date, "%Y-%m-%d").weekday()] }}
                 </td> 
                 <td>&nbsp; &nbsp;</td>
                 <td>{{ meal_name }}</td>
             </tr>
             {% endif %}
         {% endfor %}
  </table>
title: Week Menu

Is blank for some reason:

Screenshot 2024-03-23 at 16.05.05

Any ideas where I am going wrong please?

Great work. However how do you stop it adding staples like salt and olive oil to the shopping list. I only want to add those if I have actually run out.

Probably your sensor name is a bit different.
Go to Developers tools and check the States tab and filter on mealie

Or the meal_date sensor value is unknown.
Then you can also remove the if condition in the markdown template.

{% if meal_date != 'unknown' %} 
...
{% endif %}
1 Like

it worked out to be an issue with my config. Thank you.

1 Like

@nickrout When you add the recipe to a shopping list in Mealie, you can uncheck any ingredients you donā€™t need to add to your list.

1 Like

Well thatā€™s easy!

1 Like

Iā€™m loving the things people are doing with this! But I was wondering, would it be easily possible to gather a list of recipes with a certain category (for example pastaā€™s) and display them with things needed, picture and description in Home Assistant?

All that data can be gathered calling multiple mealie apiā€™s. Not really easy but itā€™s possible.

Why do you want so much information at ones on your dashboard? All ingredients, all steps and all photos.

not the steps to do, just the description and things needed with a picture and name to make it easily to scroll through in HA when you want to make dinner plans for the next week, and do it from HA instead of the website, but then again, I just noticed there is an unofficial Android app made, which makes it easierā€¦

The main idea was to have the same feeling as you would have when you used to go over your cook books page by page looking for something to make and writing down the ingredients needed :sweat_smile:

Just stopping by to say thanks, @my.spider.died! I set this up this morning and looks to be working great. This is really handy.

HA did throw a couple errors, not liking the description fields on data items (ie, description: Imported From Mealie) but that was easy to fix. Iā€™m running 2024.4.2

1 Like

Thank you for posting this solution, this would be really handy to work in HAā€¦ I am having some trouble getting this to work though. I was able to get the sensor in HA, but it seems to only pick up the last item in a list. Also with the automation it will repeat that item infinitely until i shut the automation off.
Lastly, I am not able to get the delete step to work, HA seems to not like the line " url: !secret mealie_shopping_items_api_endpoint"

What am i missing or doing wrong?

Current Items in Mealie:

Here is my api call

- platform: rest
  resource: "https://mymealie/api/groups/shopping/items"
  method: GET
  name: Mealie Shopping List Items
  headers:
    Authorization: !secret mealie_api_token
  force_update: true
  scan_interval: 30
  params:
    perPage: 1
    queryFilter: checked=false
    orderBy: food.name
    orderDirection: asc
  #As long as the request contains "total", the API was successfully reached
  availability: >
    {{ value_json['total'] }}

  #Extract the ingredient data in the following format: "food - quantity unit" eg. "apples - 1.5 kilogram", or "Empty List" if no items are left in the list
  value_template: >
    {# If no items in list, return "Empty List" #}
    {% if value_json['total'] == 0 %}
      Empty List

    {# If distinct food is not defined use display name instead #}
    {% elif value_json['items'][0]['food'] == none %}
      {{ value_json['items'][0]['display'] }}

    {# If quantity is available, include it in the output #}
    {% elif value_json['items'][0]['quantity'] > 0 %}
      {{ value_json['items'][0]['food']['name'] }} - {{ (value_json['items'][0]['quantity'] | round(3) | string).rstrip('0').rstrip('.') }} {{ value_json['items'][0]['unit']['name'] }}

    {# Otherwise, print just the food name #}
    {% else %}
      {{ value_json['items'][0]['food']['name'] }}
      
    {% endif %}

  #Extract the ingredient's ID. Required to delete through the API later on.
  json_attributes_path: "$.items.0"
  json_attributes:
    - id

Entity showing in HA, but only showing the last item (not sure if this is expected, but seems off to me)
2024-04-23 00_57_17-Developer tools ā€“ Home Assistant - Vivaldi
2024-04-23 00_57_26-Developer tools ā€“ Home Assistant - Vivaldi

rest_command:
  #Command to delete a Mealing shopping list item
  mealie_delete_from_shopping_list:
    url: >
      {{ url }}/{{ state_attr('sensor.mealie_shopping_list_items', 'id') }}
    method: delete
    headers:
      Authorization: !secret mealie_api_token

Rest command not working in the automation

Result at this point is infinatly repeating the last item in HA todo list

Update I was able to eventually get something workable for importing using the call below to import all items of a specific shopping list, then making a template sensor to put items into a listā€¦this works extremely wellā€¦ the thing is, I would now need to be able to delete or remove items once marked as completed on the list otherwise they will sink again ā€¦i have not yet figured out how to do that.

- platform: rest
  name: Mealie Shopping List Items
  method: GET
  resource_template: "https://my_domain/api/groups/shopping/lists/[myshoppinglistid]"
  headers:
    Authorization: !secret my_token
  value_template: "OK"
  json_attributes:
    - listname
    - listItems

Hey @shredder_guitarist. The sensor should only show the last item; essentially it reads the list, pulls the last item into that sensor, and then automation.mealie_transfer_shopping_list should delete it from Mealie after it has saved it to Todoist. Once deleted from mealie, the rest sensor will be reading a different value for the last item on the list from mealie (ie. no longer the deleted one), which triggers the automation to occur again. This repeats until there are no items on the Mealie list any more, giving the sensor the value ā€œEmpty Listā€, which stops the automation from running further.

For the issue youā€™re seeing, are there any errors logged under System > Logs which might hint as to why the rest_command.mealie_delete_from_shopping_list isnā€™t working for you?

One thing I can think of which could cause it is if your mealie api endpoint url in your secrets.yaml contains a trailing slash? The value should be something like:

https://my-mealie-url.com/api/groups/shopping/items

But if it has a trailing slash, then the rest_command yaml wonā€™t work, resulting in it trying to call the API endpoint with a double slash, like:

https://my-mealie-url.com/api/groups/shopping/items//9c1f9552-36e2-4a45-a989-dd55e160f807
1 Like

Iā€™m glad itā€™s working well for you @ih8gates!

Perhaps the description field is only available for certain todo list integrations. I use Todoist as my target todo list, which supports description, but perhaps your one doesnā€™t.

This has worked fantastically for me, and hugely helps with meal planning and shopping lists! So a huge thanks. It has caused a persistent warning in my logs though - ā€œJSON result was not a dictionary or list with 0th element a dictionaryā€.

Any ideas on a fix? Iā€™m transferring mealieā€™s shopping list into HAā€™s local to-do list.

Ok, After starting over again from scratch i was able to get it working after a lot of swearing, cursing, and a good cry in a cold shower.
This will be a really handy addition to automating shopping lists.

There are still some loose ends thoughā€¦

  1. The notification message doesnā€™t work in the way that itā€™s written in your example. Iā€™m not sure if those are literal quotes and brackets or placeholders; there are quite a few places in your example where I canā€™t quite tell if the brackets are supposed to be there or notā€¦like ā€˜{# If distinct food is not defined use display name instead #}ā€™ for exampleā€¦it seems like this is a comment, but also a piece of code. I removed these occurrences and it still seems to work fine. One thing Iā€™m sure of is that the message: ā€œ{{ mealie_delete_response[ā€˜contentā€™] }}ā€ is not liked in my instance for some reason, as a workaround, I just created a ā€œFailed to delete itemā€ message and left it at that.

  2. Also, Iā€™ll share that there is a slight modification to the existing code that can be done to obtain items from a specific shopping list

note the query filter and name notes

- platform: rest
  name: Mealie Shopping List Items #insert preferred shopping list name
  resource_template: !secret mealie_shopping_items_api_endpoint
  headers:
    Authorization: !secret mealie_api_token
  params:
    perPage: 1
    queryFilter: checked=false AND shoppingListId= XXXX-XXXX-XXXXXXXXX #Obtain shopping list id from the end of browser url while navigating to the shopping list in mealie 
    orderBy: food.name
    orderDirection: asc
  #As long as the request contains "total", the API was successfully reached
  availability: >
    {{ value_json['total'] }}
  #Extract the ingredient data in the following format: "food - quantity unit" eg. "apples - 1.5 kilogram", or "Empty List" if no items are left in the list
  value_template: >
    {% if value_json['total'] == 0 %}
      Empty List
    {% elif value_json['items'][0]['food'] == none %}
      {{ value_json['items'][0]['display'] }}
    {% elif value_json['items'][0]['quantity'] > 0 %}
      {{ value_json['items'][0]['food']['name'] }} - {{ (value_json['items'][0]['quantity'] | round(3) | string).rstrip('0').rstrip('.') }} {{ value_json['items'][0]['unit']['name'] }}
    {% else %}
      {{ value_json['items'][0]['food']['name'] }}  
    {% endif %}
  #Extract the ingredient's ID. Required to delete through the API later on.
  json_attributes_path: "$.items.0"
  json_attributes:
    - id

and a modification to the delete command, note the URL includes the specific shopping list id

rest_command:
  #Command to delete a Mealing shopping list item
  mealie_delete_from_shopping_list:
    url: >
      https://###mealieurl###/api/groups/shopping/items/{{ state_attr('sensor.mealie_shopping_list_items', 'id') }}?group_id=XXX-XXXXX-XXXXXX #group id is the preferred shopping list ID
    method: delete
    headers:
      Authorization: !secret mealie_api_token