Skip to content
eddiesignerPublic

About

Self-hosted playlist sync between Spotify and YouTube Music.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

You2Sync

Self-hosted playlist sync between Spotify and YouTube Music.

  • Sync one playlist, a selection, or everything, in either direction.
  • Only changes mode applies what was added/removed since the last sync; Full mirror makes the target an exact copy.
  • Playlists are paired by id, so renames on either side are fine. Unpaired playlists fall back to a same-named playlist, otherwise a new one is created.
  • Liked songs sync too (Spotify "Liked Songs" ↔ YouTube Music "Liked Music").
  • Every removal is previewed and must be confirmed.
  • Songs without a confident match are skipped and listed under Review, where you pick the right version or dismiss them.

Screenshots

Connect screen: link Spotify and YouTube Music

Library: pick playlists, direction and sync mode

How it works

Browser (Vue SPA) ──► Nuxt server API ──► services ──► providers ──► Spotify Web API
                                             │                 └──► YouTube Music (InnerTube, youtubei.js)
                                             └──► repositories ──► SQLite
  • The server is a thin JSON API. Artwork loads straight from the services' CDNs.
  • Spotify uses OAuth. YouTube Music has no public playlist API, so it is accessed through its internal API with your browser cookie.
  • Tokens and cookies are encrypted with AES-256-GCM before they hit the database and never reach the browser.
  • The first Spotify and YouTube Music accounts to connect become the instance owner. Other accounts are rejected.

Limitations

  • Spotify development mode (Feb 2026 rules): the app owner needs Premium, at most 5 allow-listed users, and only playlists you own or collaborate on can be read.
  • Covers can be copied YouTube Music → Spotify only. YouTube does not allow custom playlist covers through any API.
  • YouTube Music cookie expires when you sign out of that browser session. Paste a new one under Settings.
  • Song order is not synced.

Setup

Requires Node.js 24 (as in the Dockerfile; node:sqlite needs at least 22.13) or Docker.

  1. Create an app at the Spotify developer dashboard (Web API). Add the redirect URI:

    • http://127.0.0.1:3000/api/auth/spotify for npm run dev
    • http://127.0.0.1:8080/api/auth/spotify for Docker
    • https://your.domain/api/auth/spotify in production

    Spotify rejects localhost; use 127.0.0.1 and open the app on that same host.

  2. Copy .env.example to .env and fill it in:

    cp .env.example .env
    openssl rand -hex 32   # NUXT_SESSION_PASSWORD
    openssl rand -hex 32   # NUXT_ENCRYPTION_KEY
    Variable Required Description
    NUXT_OAUTH_SPOTIFY_CLIENT_ID Yes Client ID of the Spotify app from step 1.
    NUXT_OAUTH_SPOTIFY_CLIENT_SECRET Yes Client secret of the same app.
    NUXT_OAUTH_SPOTIFY_REDIRECT_URL Yes Must match a redirect URI from step 1 exactly. Defaults to http://127.0.0.1:3000/api/auth/spotify.
    NUXT_SESSION_PASSWORD Yes Signs the session cookie. At least 32 characters.
    NUXT_ENCRYPTION_KEY Yes Encrypts stored tokens and cookies. 32 bytes, as 64 hex characters or base64. Changing it makes stored credentials unreadable; connect both services again.
    NUXT_OWNER_SPOTIFY_ID No Locks the instance to this Spotify user id (the "Username" on your Spotify account page). Without it, the first account to connect becomes the owner.
    NUXT_DATABASE_PATH No SQLite file location. Defaults to ./data/you2sync.db.
    NUXT_SESSION_COOKIE_SECURE No Set to false only when serving production builds over plain HTTP, like the local Docker setup.

Development

npm install
npm run dev        # http://127.0.0.1:3000
Command What it does
npm install Installs dependencies. Also runs postinstall (nuxt prepare), which generates the types in .nuxt/.
npm run dev Starts the dev server with hot reload at http://127.0.0.1:3000.
npm run build Builds the production server into .output/.
npm run preview Runs the production build locally. Run npm run build first.
npm test Runs the unit tests once with Vitest.
npm run typecheck Type-checks the app and server with vue-tsc. Tests are not included.
npm run generate Nuxt's static-site export. Not usable here: the app needs its server API.

Docker + nginx

docker compose up -d --build   # http://127.0.0.1:8080

This runs the app and nginx on your machine only. To put it on a server, see Deployment.

Deployment

The Docker setup is meant for a small VPS or home server with a domain pointing at it. The app needs HTTPS in production: Spotify requires it for non-loopback redirect URIs, and the session cookie is Secure.

First, on any server:

  1. Spotify. Add https://your.domain/api/auth/spotify as a redirect URI in the Spotify app.
  2. Environment. On the server, clone the repository and create .env as in Setup, with NUXT_OAUTH_SPOTIFY_REDIRECT_URL=https://your.domain/api/auth/spotify. On a public domain, also set NUXT_OWNER_SPOTIFY_ID; otherwise whoever connects first becomes the owner.

Then pick the option that fits your server.

Option A: behind an existing Nginx

For a server that already runs Nginx and Certbot for other sites. Docker runs only the app, on localhost; your Nginx proxies the domain to it.

  1. Compose file. Create compose.prod.yml next to docker-compose.yml. It is not part of the repository, so git pull never touches it.

    # Fixed project name: the container, network and volume are prefixed
    # "you2sync", so they never collide with other Compose projects.
    name: you2sync
    
    services:
      app:
        build: .
        restart: unless-stopped
        env_file: .env
        environment:
          NUXT_DATABASE_PATH: /data/you2sync.db
        # Localhost only, so the app is reachable just through Nginx.
        # Docker's published ports bypass ufw. Change 3000 on the left
        # if another service already uses it.
        ports:
          - "127.0.0.1:3000:3000"
        volumes:
          - data:/data   # SQLite database; survives rebuilds
        mem_limit: 256m
        read_only: true
        tmpfs:
          - /tmp
    
    volumes:
      data:

    Add this line to .env, so plain docker compose commands use that file:

    COMPOSE_FILE=compose.prod.yml
  2. Start.

    docker compose up -d --build
    curl -I http://127.0.0.1:3000   # expect HTTP 200
  3. Nginx site. Create /etc/nginx/sites-available/your.domain:

    server {
      listen 80;
      listen [::]:80;
      server_name your.domain;
    
      # Small JSON API; the only large body is the YouTube Music cookie.
      client_max_body_size 64k;
    
      # Hashed build assets never change.
      location /_nuxt/ {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        add_header Cache-Control "public, max-age=31536000, immutable";
      }
    
      # Host and X-Forwarded-* are required: the app compares them with the
      # request origin and rejects every write without them.
      location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-Host $host;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      }
    }
    sudo ln -s /etc/nginx/sites-available/your.domain /etc/nginx/sites-enabled/
    sudo nginx -t && sudo systemctl reload nginx
  4. TLS. Certbot adds the HTTPS server block and the HTTP redirect to that file:

    sudo certbot --nginx -d your.domain

Day to day, from the project folder:

Task Command
Status docker compose ps
Logs docker compose logs -f
Stop / start / restart docker compose stop / start / restart
Remove the container, keep data docker compose down

A stopped app stays stopped, even after a reboot, and nothing else on the server is affected.

Option B: bundled nginx

For a server with nothing else on ports 80 and 443. docker-compose.yml runs the app and its own nginx.

  1. Compose. In docker-compose.yml, values under environment override .env. For the app service:

    • set NUXT_OAUTH_SPOTIFY_REDIRECT_URL to https://your.domain/api/auth/spotify;
    • remove NUXT_SESSION_COOKIE_SECURE: "false".

    For the nginx service, publish the public ports and mount your certificates:

    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./deploy/nginx.conf:/etc/nginx/conf.d/default.conf:ro
      - ./certs:/etc/nginx/certs:ro   # fullchain.pem and privkey.pem
  2. TLS. In deploy/nginx.conf, uncomment the 443 server block and set server_name to your domain. Replace the location blocks of the port 80 server with a redirect, so the app is only served over HTTPS:

    location / {
      return 301 https://$host$request_uri;
    }

    Certificates can come from any ACME client, e.g. certbot. Copy them into ./certs rather than mounting certbot's live directory, which contains symlinks.

  3. Start.

    docker compose up -d --build

Updates and backups

Updating: git pull && docker compose up -d --build. Data survives in the data volume.

Backups: the SQLite database lives in the data volume at /data/you2sync.db. Keep NUXT_ENCRYPTION_KEY with it; the stored credentials cannot be decrypted without it.

Without Docker

Run npm ci && npm run build, then node .output/server/index.mjs with the variables from .env set. It listens on PORT (default 3000). Put any HTTPS reverse proxy in front that forwards Host and X-Forwarded-Proto.

Connecting YouTube Music

  1. Sign in at music.youtube.com in a desktop browser.
  2. Open developer tools → Network, and click around until a browse request appears.
  3. Copy the full cookie request header and paste it into You2Sync.

Stack

Nuxt 4 (SPA mode) · Vue 3 · shadcn-vue · Tailwind CSS 4 · nuxt-auth-utils · youtubei.js · node:sqlite · Vitest

Credits

You2Sync is not affiliated with, endorsed by, or sponsored by Spotify or YouTube. Spotify and YouTube Music are trademarks of their respective owners.

Support

If You2Sync saves you time, you can buy me a coffee on Ko-fi. ☕

License

MIT © 2026 Eduardo Gomez

About

Self-hosted playlist sync between Spotify and YouTube Music.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Sponsor this project

Packages

Contributors

Languages