Personal runbook for migrating a family photo library from Synology Photos to a self-hosted Immich instance. Covers bulk upload, Google Takeout import, and album reconstruction via the Synology PostgreSQL database.
Setup#
- Source: Synology NAS running Synology Photos (multiple users)
- Destination: Immich self-hosted on the same NAS
- Upload tool: immich-go v0.31+
- Client: WSL2 on Windows, SSH access to NAS
- Album script: custom Python (
migrate_albums.py) using Immich REST API
Phase 1: Photo Uploads#
Strategy#
Two sources per user:
- Google Takeout — photos before the cutoff date (when Google Photos was primary)
- Synology folder — photos after the cutoff date (when Synology became primary)
The cutoff date is when you switched from Google Photos to Synology as your primary photo storage. Photos before that date live in Google Photos at full resolution; after that date, full-resolution is on Synology.
immich-go from-folder upload#
Run on the NAS directly — avoids copying large libraries (100 GB+) over the network.
| |
Key flags:
| Flag | Purpose |
|---|---|
--manage-raw-jpeg=StackCoverJPG | Stack RAW+JPEG pairs, JPEG as cover |
--manage-burst=Stack | Stack burst photo sequences |
--pause-immich-jobs=true | Pause ML jobs during upload (faster) |
--session-tag | Tag all uploads with session ID for tracking |
--concurrent-tasks=6 | Parallelism — tune to your NAS CPU |
--on-errors=continue | Don’t abort on individual file failures |
To upload a folder directly as an album:
| |
Google Takeout import#
Download the Takeout archive(s), then:
| |
Use --date-range=<start>,<cutoff> to limit to photos before the cutoff date
(avoids importing lower-resolution Google-compressed copies of photos you
already have on Synology).
Phase 2: Album Reconstruction#
Synology Photos stores album membership in its PostgreSQL database. After photos are in Immich, you reconstruct albums via the Immich REST API using a script that reads a TSV export from Synology’s DB.
Export album data from Synology DB#
Synology Photos uses PostgreSQL. Database: synofoto. Must run as postgres
user (peer auth — no password, but requires sudo).
| |
Note: Old psql on Synology does not support
--csv. Use-A -F$'\t' -tfor tab-separated output.
Key tables:
| Table | Purpose |
|---|---|
album | Album metadata (name, owner, shared flag) |
normal_album | Marks album as regular (non-smart) type |
many_item_has_many_normal_album | Album ↔ photo membership |
item | Photo item (logical, parent of unit) |
unit | Physical file (filename, takentime, folder FK) |
folder | Folder path |
user_info | User display names |
Physical path from DB fields:
- Personal space:
/volume1/homes/<username>/Photos<folder_path>/<filename> - Shared space:
/volume1/photo<folder_path>/<filename>
TSV columns (10 fields, tab-separated):
| |
Album migration script#
migrate_albums.py reads the TSV and recreates all albums in Immich:
- For each photo, call
POST /api/search/metadatawithoriginalFileName - If multiple filename matches, pick closest by
takentime(unix timestamp from Synology DB) - Create album via
POST /api/albumsunder the album owner’s account - Add assets via
PUT /api/albums/{id}/assets(batches of 100) - Share album via
PUT /api/albums/{id}/userswith family members (editor role)
Config block at top of script:
| |
Usage:
| |
Important notes:
- Script checks for existing album name before creating — safe if re-run, but avoid running twice as it would create duplicates if the check is bypassed
- Photos with no configured API key are skipped and reported; re-run after adding the key
- Unmatched photos (not yet uploaded) are printed but don’t block other albums
- Search is scoped per Immich user, so photo lookup must use the file owner’s API key, not the album owner’s
Verification#
After migration, verify album counts match between Synology and Immich:
query Synology DB for COUNT(*) per album, then compare against
GET /api/albums/{id} assetCount field.
Lessons Learned#
- Run immich-go on the NAS — no network bottleneck; critical for 100 GB+ libraries
- Pause Immich ML jobs during upload —
--pause-immich-jobs=truespeeds up import significantly - immich-go handles duplicates — safe to re-run; already-uploaded files are detected and skipped
- Each Immich user needs their own API key — album membership search must use the file owner’s key (search results are user-scoped)
- Filename + takentime matching is reliable —
originalFileNamesearch plus unix timestamp disambiguation works well for Synology Photos datasets - Old psql on Synology — no
--csvflag; use-A -F$'\t' -tfor TSV --session-tagin immich-go — tags all uploads with a session ID, useful for auditing which files came from which run- Album script idempotency — checks existing album names before creating; unmatched photos are non-blocking
Reference#
- Immich API docs
- immich-go docs
- Full working artifacts (scripts, TSV export, logs): private NAS repo at
nas:/volume1/homes/<admin-user>/repos/photo-migration.git