Upload Immich album originals to Google Photos via Playwright
Project description
immich-to-gphotos
Upload originals from an Immich album into an existing Google Photos album. Google has no public upload API; this tool uses Playwright with a saved browser session.
Disclaimer
This is an unofficial personal automation tool. It is not affiliated with Google or Immich. Google’s web UI changes can break uploads at any time. The saved session file (google-storage.json) is equivalent to staying logged in—protect it like a password (chmod 600, never commit it, never paste it in issues).
Requirements
- Python 3.11+
- exiftool on
PATH(metadata embedding) - Google Chrome or Microsoft Edge — Playwright drives the system browser, not a bundled Chromium download
- Immich API key with access to the source album
On Ubuntu:
sudo apt install google-chrome-stable
Install
pipx (recommended)
pipx install immich-to-gphotos
# or from GitHub:
# pipx install git+https://github.com/joyjit/immich-to-gphotos.git
# pipx install git+https://github.com/joyjit/immich-to-gphotos.git@v0.1.0
From a clone (development)
git clone https://github.com/joyjit/immich-to-gphotos.git
cd immich-to-gphotos
python3 -m venv .venv
source .venv/bin/activate
pip install -e .
First-time setup (every user)
mkdir -p ~/.local/share/immich-to-gphotos
cp immich.conf-example ~/.local/share/immich-to-gphotos/immich.conf
chmod 600 ~/.local/share/immich-to-gphotos/immich.conf
# Edit immich.conf: set IMMICH_API_KEY and IMMICH_URL
Config precedence: CLI flags → environment → immich.conf → built-in defaults.
Usage
One-time Google sign-in
immich-to-gphotos auth
A system Chrome window opens (profile under ~/.local/share/immich-to-gphotos/chrome-profile). Sign in to the Google account that owns the target albums. The session is saved to ~/.local/share/immich-to-gphotos/google-storage.json (override with IMMICH_TO_GPHOTOS_AUTH_FILE).
If Google shows “This browser or app may not be secure”, use your normal Chrome via remote debugging:
google-chrome --remote-debugging-port=9222 https://photos.google.com
# Sign in in that window, then:
immich-to-gphotos auth --cdp-url http://127.0.0.1:9222
auth needs a graphical session (local desktop or switch-user). Plain SSH without a display cannot open Chrome; use --cdp-url instead (see above).
Upload
immich-to-gphotos upload \
--immich-album "Trip 2024" \
--google-album "Trip 2024"
Optional Immich URL override:
immich-to-gphotos upload \
--immich-album "Trip 2024" \
--google-album "Trip 2024" \
--immich-url https://immich.example.com
Every upload run validates the Google session, even when Immich has nothing new:
- New files — upload to Google, then save refreshed cookies to
google-storage.json. - Nothing new — headless visit to Google Photos only; rewrite
google-storage.jsonif still signed in.
If the session is missing or expired, upload exits 4 on every run (including idle cron runs). Previously, a run with no new files could exit 0 while the Google session was already dead.
Behavior
| Topic | Behavior |
|---|---|
| Immich album | Case-insensitive name match; error if none or ambiguous |
| Asset limit | Fails if album has more than 500 assets |
| Dedup | By filename per Google album (state/<slug>.json) |
| RAW | Converted to JPEG before upload |
| HEIC/HEIF | Skipped with warning |
| Live Photo | Uploads image + motion video when Immich links them |
| exiftool errors | Warning only; file still uploaded |
| Google album | Must already exist (yours or shared with you); not created by this tool |
| Google session | Checked on every upload; cookies refreshed when signed in |
Exit codes
| Code | Meaning |
|---|---|
| 0 | Success |
| 1 | Unexpected failure |
| 2 | Missing/invalid configuration |
| 3 | Immich album not found or ambiguous |
| 4 | Google auth / session expired |
| 5 | Immich API error |
| 6 | Google Photos / browser failure |
Logs go to stderr with journal-style prefixes, e.g. <6>immich-to-gphotos: ….
Cron
Run upload on a schedule once auth has been done and immich.conf is in place. Re-run auth when uploads fail with exit code 4 (typically every few weeks, not on a fixed schedule).
Cron does not use your login PATH. Use the full path to the CLI, e.g. /home/you/.local/bin/immich-to-gphotos.
Each scheduled upload launches headless Chrome to validate or refresh the Google session. A frequent schedule (e.g. every 5 minutes) is fine on a small server but does add overhead when there is nothing to upload.
0 3 * * * /home/you/.local/bin/immich-to-gphotos upload --immich-album "Backup" --google-album "Backup" 2>&1 | logger -t immich-to-gphotos
Limitations
- Filename dedup: two different Immich assets with the same basename in one Google album — only the first is uploaded.
- Google web UI changes may break automation.
- Shared albums: you must be allowed to add photos (collaboration enabled by the owner).
- HEIC/HEIF assets are never uploaded.
- Live Photo pairing on Google is best-effort.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file immich_to_gphotos-0.1.1.tar.gz.
File metadata
- Download URL: immich_to_gphotos-0.1.1.tar.gz
- Upload date:
- Size: 17.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4435c2bef29e19bb14146be2d8209d19a1c4a16df2f58ae7597e79ce1f39ef9a
|
|
| MD5 |
a6e3b98854479a3d4e72e825454a9603
|
|
| BLAKE2b-256 |
33b2ba1b0910f2b2f867d2f01466853e0b8adaa2aae8beebf13eb08797a4ac46
|
Provenance
The following attestation bundles were made for immich_to_gphotos-0.1.1.tar.gz:
Publisher:
publish-pypi.yml on joyjit/immich-to-gphotos
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
immich_to_gphotos-0.1.1.tar.gz -
Subject digest:
4435c2bef29e19bb14146be2d8209d19a1c4a16df2f58ae7597e79ce1f39ef9a - Sigstore transparency entry: 1902464175
- Sigstore integration time:
-
Permalink:
joyjit/immich-to-gphotos@07a251fa9c28a09f69abbd19a6f51d09db985f91 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/joyjit
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-pypi.yml@07a251fa9c28a09f69abbd19a6f51d09db985f91 -
Trigger Event:
push
-
Statement type:
File details
Details for the file immich_to_gphotos-0.1.1-py3-none-any.whl.
File metadata
- Download URL: immich_to_gphotos-0.1.1-py3-none-any.whl
- Upload date:
- Size: 20.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
be5f2ab4f98bd665338b47068cebfcdbac1322119da35c3cbe315f8344336a6a
|
|
| MD5 |
1f75cbafacc091621b41228a5753c3d4
|
|
| BLAKE2b-256 |
a682907df7939c14cb3a7e4ed36211dac569b1914a1c5610d589fa2743abb8a1
|
Provenance
The following attestation bundles were made for immich_to_gphotos-0.1.1-py3-none-any.whl:
Publisher:
publish-pypi.yml on joyjit/immich-to-gphotos
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
immich_to_gphotos-0.1.1-py3-none-any.whl -
Subject digest:
be5f2ab4f98bd665338b47068cebfcdbac1322119da35c3cbe315f8344336a6a - Sigstore transparency entry: 1902464285
- Sigstore integration time:
-
Permalink:
joyjit/immich-to-gphotos@07a251fa9c28a09f69abbd19a6f51d09db985f91 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/joyjit
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-pypi.yml@07a251fa9c28a09f69abbd19a6f51d09db985f91 -
Trigger Event:
push
-
Statement type: