** Full disclosure: Vibe-Coded **
Home Assistant custom integration to request Movies and TV shows via:
- Seerr backend (requests go to Seerr which talks to your Arr apps)
- Direct Arr backend (requests go straight to Radarr/Sonarr)
It fetches servers/roots/profiles from your backend and exposes them as Select entities. Service calls use those entities as defaults, so you can change behavior on the fly without editing YAML.
- Two* backends: Seerr or Direct Arr (Sonarr+Radarr)
- Auto-fetch and expose choices as Select entities:
- Seerr: default Radarr/Sonarr server, default Movie/TV profiles
- Arr: default Radarr/Sonarr root folders, default quality profiles
- Default TV Seasons as a Select entity (Season 1 or All)
- “Backend in use” sensor (informational)
- Clean device grouping per entry (entities appear under one device)
- Single service: hassarr.request_media (uses entity selections as defaults)
- Accepts “tmdb:” in query for precision
- Clear error messages and HA events on success/failure
- HACS (recommended): Add this repository as a Custom Repository, then install “Hassarr Media Requests”.
- Manual: Copy custom_components/hassarr into your Home Assistant config/custom_components directory and restart Home Assistant.
Pick one backend per entry. Each backend uses a 4-step guided setup.
Seerr backend:
- Base URL & API Key
- Default Server
- Seerr Radarr Default Server
- Seerr Sonarr Default Server
- Default Profile
- Seerr Default Movie Profile
- Seerr Default TV Profile
- Default TV Season
- Season 1 or All Seasons
Arr backend (direct Sonarr/Radarr):
- URLs & API Keys (Radarr, Sonarr)
- Default Root Folders (Radarr root, Sonarr root; dropdowns)
- Default Profiles (Radarr quality, Sonarr quality)
- Default TV Season (Season 1 or All)
After setup, entities are created under a device named:
- “Hassarr (Seerr)” or
- “Hassarr (Sonarr/Radarr)”
Select entities (used as service defaults):
- Seerr:
- Hassarr Radarr Server (serverId)
- Hassarr Sonarr Server (serverId)
- Hassarr Movie Profile (profileId)
- Hassarr TV Profile (profileId)
- Arr:
- Hassarr Radarr Root (path)
- Hassarr Radarr Quality Profile (id)
- Hassarr Sonarr Root (path)
- Hassarr Sonarr Quality Profile (id)
- Common:
- Hassarr Default TV Seasons (season1 | all)
Sensor:
- Hassarr Backend (seerr | arr), informational only
Fields (see services.yaml):
| Field | Required | Notes |
|---|---|---|
| query | yes | Title or tmdb:<id> |
| media_type | yes | movie, or tv |
| seasons | no | TV only: all or list [1,2]; omitted = Default TV Seasons Entity |
| is_4k | no | Seerr backend only, untested |
| seerr_server_id / seerr_profile_id / overseer_user_id | no | Override defaults (Seerr backend), default = Entity |
| quality_profile_id / root_folder_path | no | Override Defaults (ARR backend), default = Entity |
Examples:
# Minimal Seerr movie
service: hassarr.request_media
data:
query: "Dune (2021)"
media_type: movie
# Minimal Arr TV (uses select entities and default seasons)
service: hassarr.request_media
data:
query: "The Bear"
media_type: tv
# Precise by TMDB id
service: hassarr.request_media
data:
query: "tmdb:1399"
media_type: tv
# One-off overrides (Arr)
service: hassarr.request_media
data:
query: "Alien"
media_type: movie
quality_profile_id: 7
root_folder_path: "/data/movies"alias: Media Request
mode: single
triggers:
- trigger: conversation
command:
- Download show {title} season {seasons}
- Download show {title} seasons {seasons}
- Download show {title}
id: show
- trigger: conversation
command:
- Download movie {title}
id: movie
actions:
- choose:
- conditions:
- condition: trigger
id: movie
sequence:
- variables:
title_clean: >-
{{ (trigger.slots.title | default(trigger.sentence, true))
| regex_replace('(?i)^\\s*download\\s+movie\\s+', '')
| regex_replace('(?i)\\s+seasons?\\s+.*$', '')
| trim }}
- action: hassarr.request_media
data:
media_type: movie
query: "{{ title_clean }}"
- conditions:
- condition: trigger
id: show
sequence:
- variables:
# Start from the slot title, fall back to the sentence
title_raw: "{{ trigger.slots.title | default(trigger.sentence, true) }}"
# Strip leading keyword and any trailing "season(s) …"
title_clean: >-
{{ title_raw
| regex_replace('(?i)^\\s*download\\s+show\\s+', '')
| regex_replace('(?i)\\s+seasons?\\s+(all|\\d+(?:\\s*,\\s*\\d+)*)\\s*$', '')
| trim }}
# Prefer captured slot; else extract from the sentence
seasons_raw: >-
{% if trigger.slots.seasons is defined %}
{{ trigger.slots.seasons }}
{% else %}
{{ (trigger.sentence | regex_findall_index('(?i)\\bseasons?\\s+((?:all|\\d+(?:\\s*,\\s*\\d+)*))', 0))
| default('', true) }}
{% endif %}
seasons_parsed: >-
{% set s = (seasons_raw | string | trim) %}
{% if not s %}
{{ omit }}
{% elif s | lower == 'all' %}
all
{% else %}
[{{ (s | regex_findall('\\d+') | map('int') | list) | join(', ') }}]
{% endif %}
- action: hassarr.request_media
data:
media_type: tv
query: "{{ title_clean }}"
seasons: "{{ seasons_parsed }}"