Scrob syncs your libraries from Jellyfin, Plex, Emby, and Nuvio, tracks your watch history, ratings, and personal lists, and can push watched activity back to connected providers - all from a clean, app-like web interface that installs as a PWA on any device.
- Features
- Screenshots
- Getting Started
- Configuration
- Nuvio Cloud Synchronization
- MDBList Synchronization
- Webhooks
- OIDC / Single Sign-On
- Email Validation & SMTP
- Contributing
- Contributors
- Development
- License
- Multi-source sync: Import libraries and watch history from Jellyfin, Plex, Emby, and Nuvio. Nuvio also imports playback progress for Continue Watching.
- Keep providers in sync: Keep watched status synchronized between your media servers and Nuvio. Supports multiple instances and Nuvio profiles.
- Real-time scrobbling: Webhooks from Jellyfin, Plex, Emby, and Kodi update your watch state as you play - no manual sync needed.
- Manual scrobble: Start a watching session directly from any movie or episode page. Pause, resume, stop, or mark as watched - session progress shows live on the home screen.
- Trakt integration: Sync your watched history and ratings from Trakt, and push Scrob activity back to Trakt automatically.
- Simkl integration: Sync your watched history and ratings from Simkl, and push Scrob activity back to Simkl automatically.
- MDBList integration: Pull watched history, ratings, and watchlist items from MDBList, and optionally push Scrob changes back using an MDBList API key.
- Watch history & ratings: Track every movie and episode you've watched, including multiple plays with individual timestamps. Log plays manually with a custom date, or remove individual entries — all from the watched button on any movie or episode page. Rate them on a 10-point scale with optional reviews.
- Season ratings: Rate individual seasons separately from the overall show.
- Personal lists: Create and curate lists of movies and shows. Mark them public to share with other users on the same instance.
- Comments: Leave comments on movies, shows, seasons, and episodes.
- Social: Follow other users and see their activity.
- Release schedule: Movie pages show the full release schedule ��� theatrical, digital, and physical dates — sourced from TMDB.
- TMDB integration: Rich metadata for every title - posters, backdrops, cast, crew, trailers, collections, and more.
- Search: Search TMDB across movies, shows, people, and collections, merged with your local library data.
- Pick a Movie / Pick a Show: Get a suggestion on what to watch next from your library or your streaming services based on your preferences.
- Trending & Airing Today: Daily trending movies and shows from TMDB, plus episodes airing today filtered to your collection.
- Continue Watching & Next Up: Dashboard cards showing in-progress items and the next episode to watch in each series.
- Season & episode tracking: Detailed season views with per-episode watched state and progress.
- Cast & crew pages: Full filmography for any person, linked to your library.
- Radarr & Sonarr integration: Add movies and shows to Radarr/Sonarr directly from the Scrob UI.
- Plex watchlist automation: Automatically send items from your Plex watchlist (and selected friends' watchlists) to Radarr or Sonarr.
- Two-Factor Authentication: TOTP-based 2FA with backup codes, managed from the settings page.
- OIDC / SSO: Authenticate with any OpenID Connect provider (Authelia, Authentik, Keycloak, etc.).
- Progressive Web App: Install Scrob on any device - Android, iOS, or desktop - for a native app feel.
- Single container: Frontend and backend ship as one image on one port. No separate services to manage.
View more screenshots
- Docker and Docker Compose
- A TMDB Read Access Token (free) - used for metadata, search, and images
Images are hosted on Docker Hub (
bellamy/scrob). A mirror is also available on GHCR (ghcr.io/ellite/scrob) if you prefer.
- Download the compose file:
curl -o docker-compose.yaml https://raw.githubusercontent.com/ellite/scrob/main/docker-compose.yaml- Edit
docker-compose.yamland replace the required values:
services:
scrob-db:
container_name: scrob-db
image: postgres:16-alpine
restart: unless-stopped
environment:
POSTGRES_USER: scrob
POSTGRES_PASSWORD: changeme # ← change this
POSTGRES_DB: scrob
volumes:
- db_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U scrob -d scrob"]
interval: 5s
timeout: 5s
retries: 10
scrob:
container_name: scrob
image: bellamy/scrob:latest
restart: unless-stopped
depends_on:
scrob-db:
condition: service_healthy
ports:
- "7330:7330"
environment:
DATABASE_URL: postgresql+asyncpg://scrob:changeme@scrob-db:5432/scrob # ← match password above
SECRET_KEY: changeme # ← generate with: openssl rand -hex 32
TZ: UTC
volumes:
- scrob_data:/app/backend/data
volumes:
db_data:
scrob_data:- Start:
docker compose up -dThe omnibus image bundles PostgreSQL inside the container — no separate database service needed. It's the simplest way to get started, especially on platforms like Unraid or Portainer where managing multiple containers is cumbersome.
Image tags:
bellamy/scrob:latest-omnibus/ghcr.io/ellite/scrob:latest-omnibus
- Download the omnibus compose file:
curl -o docker-compose.yml https://raw.githubusercontent.com/ellite/scrob/main/docker-compose.omnibus.yml- Edit it and set your
SECRET_KEY:
SECRET_KEY: changeme # ← generate with: openssl rand -hex 32- Start:
docker compose up -dThat's it — no database container, no DATABASE_URL to configure. PostgreSQL is initialised automatically on first run and persisted in the scrob_db volume.
Switching to an external database later: set DATABASE_URL in the environment and the embedded PostgreSQL will be skipped entirely. The omnibus image behaves identically to the standard image when DATABASE_URL is provided.
Note: The embedded PostgreSQL version is tied to the image's base OS (Debian Bookworm ships PostgreSQL 15). Major version upgrades of the bundled database require a manual data migration. If you anticipate needing to control the database version independently, use the standard two-container setup instead.
Standard image (requires a separate PostgreSQL container):
# Create a dedicated network
docker network create scrob-net
# Start the database
docker run -d \
--name scrob-db \
--network scrob-net \
--restart unless-stopped \
-e POSTGRES_USER=scrob \
-e POSTGRES_PASSWORD=changeme \
-e POSTGRES_DB=scrob \
-v scrob_db:/var/lib/postgresql/data \
postgres:16-alpine
# Start Scrob
docker run -d \
--name scrob \
--network scrob-net \
--restart unless-stopped \
-p 7330:7330 \
-e DATABASE_URL="postgresql+asyncpg://scrob:changeme@scrob-db:5432/scrob" \
-e SECRET_KEY="$(openssl rand -hex 32)" \
-e TZ=UTC \
-v scrob_data:/app/backend/data \
bellamy/scrob:latestOmnibus image (PostgreSQL included — no separate container needed):
docker run -d \
--name scrob \
--restart unless-stopped \
-p 7330:7330 \
-e SECRET_KEY="$(openssl rand -hex 32)" \
-e TZ=UTC \
-v scrob_data:/app/backend/data \
-v scrob_db:/app/postgres/data \
bellamy/scrob:latest-omnibus- Open
http://localhost:7330and create your account. - Go to Settings → Integrations to add your TMDB Read Access Token and connect Jellyfin, Plex, Emby, or Nuvio.
- Select which media-server libraries to sync, or select a Nuvio profile, then trigger your first sync.
For Nuvio, choose Nuvio as the provider, sign in, and select one of the returned profiles. See Nuvio Cloud Synchronization for credential handling, sync directions, scheduling, and current limitations.
docker compose pull && docker compose up -dDatabase migrations run automatically on startup - no manual steps required.
| Variable | Default | Description |
|---|---|---|
SECRET_KEY |
- | Required. JWT signing key. Generate with openssl rand -hex 32. |
DATABASE_URL |
- | Required (standard image). PostgreSQL connection string (postgresql+asyncpg://...). Optional on the omnibus image — if omitted, the embedded database is used. |
ENABLE_REGISTRATIONS |
true |
Allow new users to register. The first user can always register regardless of this setting. |
REGISTRATION_MAX_ALLOWED_USERS |
0 |
Maximum number of registered users. 0 = unlimited. |
TZ |
UTC |
Container timezone (e.g. Europe/Lisbon). |
PUID |
1000 |
User ID to run the process as. |
PGID |
1000 |
Group ID to run the process as. |
BACKEND_PORT |
7331 |
Internal port the backend binds to. Override only if 7331 conflicts on bare metal. |
OIDC_ENABLED |
false |
Enable OIDC login. |
OIDC_DISABLE_PASSWORD_LOGIN |
false |
Enforce OIDC-only login (disables username/password). |
See docker-compose.yaml for the full list of OIDC variables and other variables.
Scrob listens on port 7330. Place a reverse proxy (Caddy, Nginx, Traefik) in front for HTTPS - required for the PWA install prompt on non-localhost addresses.
# Caddyfile
scrob.yourdomain.com {
reverse_proxy localhost:7330
}
Remove the scrob-db service and set DATABASE_URL to your existing instance:
DATABASE_URL: postgresql+asyncpg://user:password@your-db-host:5432/scrobScrob connects to the Nuvio public Cloud API at https://api.nuvio.tv. A TMDB Read Access Token must be configured in Scrob so Nuvio content identifiers can be matched to movies and shows.
- Open Settings → Media & Cloud Connections and select Add Connection.
- Choose Nuvio, then enter a connection name, your Nuvio email, and your Nuvio password.
- Select Test to authenticate and load the profiles attached to the account.
- Select the Nuvio profile to synchronize, choose the pull and push options, then select Add.
Scrob exchanges the email and password for a refresh token. The password is never persisted. Refresh-token rotation is handled automatically during connection checks and synchronization.
Each connection targets one Nuvio profile. Add another connection if you need to synchronize another profile from the same account.
| Direction | Setting | Behavior |
|---|---|---|
| Nuvio → Scrob | Collection status | Imports the profile's library movies and series. |
| Nuvio → Scrob | Watched status | Imports watched movies and episodes with their latest watch timestamps. |
| Nuvio → Scrob | Playback progress | Imports position and duration into Continue Watching. |
| Scrob → Nuvio | Watched status | Pushes watched and unwatched changes made in Scrob or imported from another connected provider. |
| Scrob → Nuvio | Playback progress | Pushes current playback positions into Nuvio's Continue Watching state as non-destructive upserts. |
Sync now runs an inbound synchronization using the enabled Nuvio → Scrob settings. Push sends the enabled watched-history and playback-progress data from Scrob to Nuvio. Both operations use non-destructive upserts; items absent from Scrob are not removed from Nuvio.
Library membership is currently pull-only. Ratings are not synchronized with Nuvio.
Auto Pull repeats the enabled inbound synchronization every 15 minutes, 30 minutes, 1 hour, 3 hours, 6 hours, 12 hours, 24 hours, or 48 hours. Nuvio synchronization is polling-based; Nuvio does not use the media-server webhook URLs documented below.
Inbound Nuvio identifiers are normalized to TMDB for Scrob's internal matching. Before an outbound push, Scrob resolves those TMDB identifiers to Nuvio-compatible bare IMDb identifiers (tt...) and caches the mapping. Unsupported identifiers are skipped rather than attached to the wrong title.
- Open Settings → Connections → MDBList.
- Copy the API key from MDBList Preferences, paste it into Scrob, and select Save Changes.
- Choose the data to import under MDBList → Scrob, then select Pull. MDBList pulls run only when this button is selected.
- To send changes back, enable the required Scrob → MDBList options. Watched-state and rating edits are pushed as they happen; edits to the managed MDBList - Watchlist are pushed to the MDBList watchlist.
The manual Push action sends the complete enabled watched, ratings, or managed-watchlist snapshot. MDBList pagination follows next_cursor and requests the documented maximum of 1,000 items per page.
Webhooks update your watch history and Continue Watching in real time. Each user's webhook URL is shown in Settings next to the relevant integration.
# Jellyfin, Plex, Emby - connection_id is shown in Settings next to each server
https://your-scrob-url/api/proxy/webhooks/{jellyfin|plex|emby}/{connection_id}?api_key=YOUR_API_KEY
# Kodi - no connection, just the API key
https://your-scrob-url/api/proxy/webhooks/kodi?api_key=YOUR_API_KEY
- In Jellyfin, go to Dashboard → Plugins → Catalogue, install Webhook, then restart.
- Go to Dashboard → Plugins → Webhook → Add Generic Destination.
- Paste your Scrob Jellyfin webhook URL.
- Enable notification types:
Playback Start,Playback Progress,Playback Stop,Mark Played. - Enable item types:
MoviesandEpisodes. - Leave the Template field blank and check "Send all properties (ignore templates)".
Do not use a custom template - Jellyfin's template engine produces invalid JSON. "Send all properties" sends a well-formed payload that Scrob parses correctly.
Plex webhooks require a Plex Pass subscription.
- Go to plex.tv/account → Webhooks → Add Webhook.
- Paste your Scrob Plex webhook URL.
- In Scrob → Settings, enter your Plex username so events are attributed to the right account.
- In Emby, go to Dashboard → Notifications → Add Notification → Webhook.
- Paste your Scrob Emby webhook URL.
- Enable events:
Playback Start,Playback Progress,Playback Stop.
Kodi scrobbling uses the scrob-kodi add-on - no manual webhook configuration needed.
- Install the scrob-kodi add-on from the scrob-kodi repository.
- In the add-on settings, enter your Scrob URL and your API key (found in Settings → Account).
- The add-on will automatically send playback events to Scrob as you watch.
Scrob supports any OpenID Connect provider (Authelia, Authentik, Keycloak, Google, etc.).
OIDC_ENABLED: "true"
OIDC_PROVIDER_NAME: "Authelia"
OIDC_CLIENT_ID: "scrob"
OIDC_CLIENT_SECRET: "your-secret"
OIDC_AUTH_URL: "https://auth.yourdomain.com/api/oidc/authorization"
OIDC_TOKEN_URL: "https://auth.yourdomain.com/api/oidc/token"
OIDC_USERINFO_URL: "https://auth.yourdomain.com/api/oidc/userinfo"
OIDC_REDIRECT_URL: "https://scrob.yourdomain.com/oidc-callback"
OIDC_AUTO_CREATE_USERS: "true"
# OIDC_DISABLE_PASSWORD_LOGIN: "true" # uncomment to enforce SSO-onlyRegister Scrob as a client in your provider with redirect URI: https://scrob.yourdomain.com/oidc-callback
Scrob can require new users to verify their email address before logging in. Providing SMTP settings also enables the forgot password link on the login page.
REQUIRE_EMAIL_VALIDATION: "true"
SERVER_URL: "https://scrob.yourdomain.com"
SMTP_ADDRESS: "smtp.gmail.com"
SMTP_PORT: "587"
SMTP_ENCRYPTION: "tls"
SMTP_USERNAME: "myemail@gmail.com"
SMTP_PASSWORD: "your-app-password"
FROM_EMAIL: "myemail@gmail.com"| Variable | Default | Description |
|---|---|---|
REQUIRE_EMAIL_VALIDATION |
false |
Require new users to verify their email before logging in. |
SERVER_URL |
- | Public URL of your Scrob instance, used to build the validation link in emails. |
SMTP_ADDRESS |
- | SMTP server hostname. |
SMTP_PORT |
587 |
SMTP server port. |
SMTP_ENCRYPTION |
tls |
Encryption method - tls or ssl. |
SMTP_USERNAME |
- | SMTP login username. |
SMTP_PASSWORD |
- | SMTP login password (use an app password if using Gmail). |
FROM_EMAIL |
- | Address emails are sent from. |
Contributions are welcome - whether it's a bug report, a feature request, or a pull request.
- Issues: Open an issue for bugs, questions, or feature ideas.
- Pull Requests: Fork the repo, create a branch, and submit a PR. Please follow the existing code style (Astro components for UI, FastAPI for backend) and make sure all browser-initiated API calls go through
/api/proxy/.
Commit messages follow Conventional Commits - feat:, fix:, chore: - as releases and changelogs are generated automatically from them.
View instructions
- Python 3.12+, uv
- Node.js 22+
- PostgreSQL 16 (via Docker is easiest)
git clone https://github.com/ellite/scrob.git
cd scrob
# Start a local database
docker compose -f docker-compose-test-db.yaml up -d
# Copy and fill in the environment file
cp .env.example .env
# Edit .env - set POSTGRES_* and SECRET_KEY at minimumcd backend
uv sync
uv run alembic upgrade head
uv run uvicorn main:app --reload --port 7331cd frontend
npm install
npm run devThe frontend dev server starts on http://localhost:4321 and proxies API calls to the backend on 7331.
Scrob is licensed under the GNU General Public License v3.0.
You are free to use, modify, and distribute Scrob, provided that any derivative works are also released under the GPLv3.
- The author: henrique.pt
- Scrob Landingpage: scrob.app
- Join the conversation: Discord Server











