Immich Photo Frame
A wall-mounted photo slideshow powered by self-hosted Immich photos, displayed on a custom Lovelace card.
Overview
Wall-mounted displays throughout the house cycle through personal photos sourced from a self-hosted Immich photo library. The system is designed for reliability — no broken images, even during photo refreshes.
Two AppDaemon apps work together:
Immich Fetcher
The immich_fetcher app periodically queries the Immich API and downloads a batch of photos to the shared /media/ NFS mount. It supports filtering by album, people, location, favorites, and dates. Filters rotate automatically each fetch cycle, and individual filters can be paused from the dashboard card — paused filters stay configured but are skipped until resumed, making it easy to keep a library of filters and switch between them.
For birthday memories, choose All Photos, select the person, and add their birthday under On these days · every year. This finds photos taken on that date across all years without relying on a smart-search description. Add several ranges for holidays: December 24 through 25 includes both Christmas Eve and Christmas Day. Ranges include both endpoints and can cross New Year; the existing Taken After / Before limits can narrow the years. The card shows invalid settings before saving and keeps your edits if a save fails.
Photo Frame Viewer
The photo_frame_viewer app manages the slideshow lifecycle:
- Poll — watches the fetcher's output directory for new photos
- Stage — atomically copies photos to a versioned generation directory via HA
shell_command - Verify — checks over HTTP that Home Assistant is really serving the new generation before trusting it
- Display — publishes the current image URL on an HA sensor that the dashboard card reads
- Swap — on the next slide advance, switches to the new generation and cleans up the old one
This generation-swap pattern means a paused slideshow keeps its current image visible indefinitely, even after multiple batch refreshes from Immich.
The verification step exists because copying a batch across NFS can occasionally outlast Home Assistant's 60-second limit on shell commands. Rather than assume a fixed delay is enough, the app polls the exact image URL the card will load and only adopts the new generation once it answers. A staging attempt that never lands is discarded and retried, so the display never shows a broken image.
Architecture
Immich Server
│
▼
immich_fetcher (AppDaemon)
│ writes to /media/immich-photos/
▼
photo_frame_viewer (AppDaemon)
│ stages to /config/www/photo-frame/live/<gen>/
│ via HA shell_command
│ then verifies over HTTP that HA serves /local/photo-frame/live/<gen>/
▼
sensor.wall_display_photo_frame_status
│ image_url + cache_bust attributes
▼
Custom Lovelace card (wall display dashboard)
Dashboard card
The viewer uses a custom:photo-display-card element with pause/next/previous controls via a relay script — works on non-admin wall display accounts. Settings are managed through a companion settings popup card.
cache_bust is derived from the file's modification time (mtime), so the browser reloads the image when it changes without requiring a full page refresh.
| Card | Path |
|---|---|
| Viewer + controls | home-assistant/cards/global/photo-frame-viewer/wall-display-photo-frame-viewer.yaml |
| Settings popup | home-assistant/cards/global/photo-frame-viewer/wall-display-photo-frame-settings.yaml |
Configuration
See the app READMEs for full configuration:
appdaemon/apps/immich_fetcher/README.mdappdaemon/apps/photo_frame_viewer/README.md