Skip to content

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:

  1. Poll — watches the fetcher's output directory for new photos
  2. Stage — atomically copies photos to a versioned generation directory via HA shell_command
  3. Verify — checks over HTTP that Home Assistant is really serving the new generation before trusting it
  4. Display — publishes the current image URL on an HA sensor that the dashboard card reads
  5. 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.md
  • appdaemon/apps/photo_frame_viewer/README.md