Automatically sync tracks between playlists - from your Liked Songs, your own playlists, or any public playlist on Spotify.
Want to share your liked tracks as a public playlist? Create a backup that never loses tracks? Mirror someone else's playlist to your own? SpotiSync handles it all.
- Sync your Liked Songs to any playlist you own
- Use any public Spotify playlist as a source (even ones you don't own!)
- Sync between your own playlists
- Archive mode: keep tracks even after their removal from the source playlist (
skip_removals) - Filter out local files, podcasts, and specific tracks
- Create artist-specific playlists with include filters
- Preview changes before applying (
dry-runmode) - Run on a schedule with Docker
- Go to the Spotify Developer Dashboard
- Click Create app
- Set Redirect URI to
https://example.com/callbackThis is the default. If you change it, update
spotify.redirect_uriin your config to match exactly. - Note down the App's Client ID and Client Secret
Required scopes (configured automatically):
user-library-read- Read your Liked Songsplaylist-read-private- Read private playlistsplaylist-modify-public/playlist-modify-private- Modify playlists
Install SpotiSync with pipx to run it as a standalone tool without affecting your system Python:
pipx install spotisyncOr with pip in a virtual environment:
pip install spotisyncNote: I recommend
pipxfor global CLI installation!
Or with Poetry (for development):
git clone https://github.com/bulletinmybeard/spotisync.git
cd spotisync
poetry installspotisync initThis guides you through:
- Entering your Spotify credentials
- Choosing a source (Liked Songs or a playlist)
- Selecting a target playlist
- Configuring filters (optional)
spotisync authA browser window opens to authorize SpotiSync with your Spotify account.
# Preview what will change
spotisync sync --dry-run
# Run the sync
spotisync sync| Command | Description |
|---|---|
spotisync init |
Interactive setup wizard |
spotisync init -o PATH |
Setup with custom config output path |
spotisync auth |
Authenticate with Spotify |
spotisync auth --clear |
Clear stored authentication token |
spotisync sync |
Run all enabled sync groups |
spotisync sync --dry-run |
Preview changes without applying |
spotisync sync -n NAME |
Sync specific group(s) by name |
spotisync sync --json |
JSON output for automation |
spotisync status |
Show auth and config status |
spotisync status --json |
JSON output |
spotisync config |
Display current configuration (YAML) |
spotisync config --json |
Display as JSON |
spotisync add |
Add a new sync group (wizard) |
Global option: --config/-c PATH to override config file location.
SpotiSync supports JSON output for scripting and CI/CD:
# Check sync results programmatically
spotisync sync --json | jq '.summary'
# Sync specific groups with JSON output
spotisync sync -n "daily-backup" -n "discover-weekly" --json
# Get authentication status
spotisync status --json | jq '.authentication.authenticated'Exit codes: 0 on success, 1 on error.
SpotiSync uses a config.yaml file. The setup wizard creates this for you, or see config.example.yaml for all options.
Multiple sync groups - Sync different sources to different targets:
sync_groups:
- name: liked-to-public
source: liked_tracks
target: 3cEYpjA9oz9GiPac4AsH4n
- name: discover-backup
source: 37i9dQZEVXcQ9COmYvdajy # Any public playlist (e.g., Spotify's Discover Weekly)
target: 5Rrf7mqN8uus2AaQQQNdc1Config file locations (checked in order):
- Docker:
/app/config.yaml - Development (git repo):
./config.yaml - User home:
~/.spotisync/config.yaml
Override with --config/-c PATH on any command.
How to get a playlist ID: Open the playlist in Spotify, click Share → Copy link. The ID is the string after
/playlist/:https://open.spotify.com/playlist/37i9dQZEVXcQ9COmYvdajy→37i9dQZEVXcQ9COmYvdajy
SpotiSync supports multiple different filter options to dictate which tracks should be included or excluded from the sync.
Include filters - Create artist-specific playlists from Liked Songs:
sync_groups:
- name: eddie-unchained
source: liked_tracks
target: 3cEYpjA9oz9GiPac4AsH4n # Your target playlist ID
filters:
include:
artists:
- 6mdiAmATAx73kdxrNrnlao # Iron MaidenInclude filters - Filter a public playlist to specific artists:
sync_groups:
- name: doom-eternal-uncluttered
source: 6s4aGjq9b42OP4nMGNCLUu # DOOM Eternal soundtrack (public playlist)
target: 9pXbKfV5dQ3sLz2nTj1uRw # Your custom playlist
filters:
include:
artists:
- 13ab1LgQZ3tQOhkDRRYB8Y # Mick GordonExclude filters - Skip unwanted tracks:
sync_groups:
- name: no-remasters
source: 6vr1Nnese49l0hxQEljOQn # Your source playlist ID
target: 3cEYojA9oz9hiPac4AsH4n # Your target playlist ID
filters:
exclude:
tracks:
- '(?i)\bremaster(?:ed)?(?:\s+\d{4})?\b' # e.g., Metallica / Enter Sandman - Remastered 2021Combined filters - Use both include and exclude:
filters:
exclude:
artists:
- '(?i)christmas' # Regex patterns
albums:
- 0sNOF9WDwhWunNAHPD3Baj # Spotify IDs
tracks:
- '(?i)remix'
- '(?i)live'Other filters:
skip_podcasts: true- Skip podcast episodes (default)
Include filters are applied before exclude filters, so you can create an artist playlist and still exclude particular tracks.
Filter inheritance: Per-group filters merge with root filters:
skip_podcasts: Per-group value overrides rootinclude/excludelists: Combined (both root and group patterns apply)
Want a backup playlist that never loses tracks? Use skip_removals to keep tracks in the target even after removing them from the source:
sync_groups:
- name: liked-tracks-archive
source: liked_tracks
target: 3cEYpjA9oz9GiPac4AsH4n # Your target playlist ID
skip_removals: true # Tracks removed from the source stay in the target playlistThis is useful for:
- Backup playlists that hold all tracks you've ever liked
- Preserving a history of tracks from a dynamic source playlist
Spotify tracks often include version markers in their titles like "(Live)", "- Remastered 2021", or "(Acoustic)". Regular expressions (regex) let you match these patterns flexibly to include or exclude specific track versions.
Regex Basics & Common Patterns
The patterns below use these common regex features:
| Syntax | Meaning | Example |
|---|---|---|
(?i) |
Case-insensitive | (?i)live matches "Live", "LIVE", "live" |
\b |
Word boundary | \blive\b matches "Live" but not "Oliver" |
(a|b) |
Either a or b | (remix|live) matches either |
? |
Optional | remaster(ed)? matches "remaster" or "remastered" |
\d{4} |
Four digits | Matches years like "2021" |
YAML tip: Use single quotes around patterns to avoid escaping backslashes.
| Pattern | Matches | Example Track Title |
|---|---|---|
'(?i)\bremaster(ed)?(\s+\d{4})?\b' |
Remastered versions | "Enter Sandman - Remastered 2021" |
'(?i)\blive\b' |
Live recordings | "Nothing Else Matters (Live)" |
'(?i)acoustic' |
Acoustic versions | "Layla (Acoustic)" |
'(?i)remix' |
Remixes | "Blinding Lights (Remix)" |
'(?i)\bdemo\b' |
Demo recordings | "Bohemian Rhapsody (Demo)" |
'(?i)instrumental' |
Instrumentals | "Stairway to Heaven (Instrumental)" |
'(?i)radio edit' |
Radio edits | "Purple Haze - Radio Edit" |
'(?i)(deluxe|bonus)' |
Deluxe/bonus tracks | "Album Name (Deluxe Edition)" |
'(?i)(feat\.|ft\.|featuring)' |
Featuring artists | "Song (feat. Artist)" |
This repo includes docker-compose.yml for scheduled syncing.
# Start the container (runs cron daemon)
docker compose up -d
# Run a manual sync
docker exec spotisync spotisync sync
# Check status
docker exec spotisync spotisync statusVolume mounts required:
./config.yaml:/app/config.yaml- Your configuration./tokens.json:/app/tokens.json- Auth tokens (created afterspotisync auth)
Configure the schedule in config.yaml:
cron:
enabled: true
schedule: "0 * * * *" # Every hourNote: Container name is
spotisync- matches the shell wrapper functions below.
Note: This section is only relevant if you run SpotiSync from the cloned repo via
poetry run. If you installed it viapiporpipx, just usespotisyncdirectly.
These optional wrapper functions provide a convenient spotisync shortcut when running from a cloned repo.
Linux/macOS (ZSH/Bash)
Create ~/spotisync_shell.sh:
For Poetry:
spotisync() {
local project_dir="$HOME/path/to/spotisync"
if [[ ! -d "$project_dir" ]]; then
echo "Error: spotisync project not found at $project_dir"
return 1
fi
(cd "$project_dir" && poetry run spotisync "$@")
}For Docker:
spotisync() {
if ! docker ps --format '{{.Names}}' | grep -q 'spotisync'; then
echo "Error: spotisync container is not running"
return 1
fi
docker exec -it spotisync spotisync "$@"
}Then add to ~/.zshrc or ~/.bashrc:
[ -f "$HOME/spotisync_shell.sh" ] && source "$HOME/spotisync_shell.sh"Reload: source ~/.zshrc
Windows (PowerShell)
Create ~\spotisync_shell.ps1:
For Poetry:
function spotisync {
$projectDir = "$env:USERPROFILE\path\to\spotisync"
if (-not (Test-Path $projectDir)) {
Write-Error "spotisync project not found at $projectDir"
return
}
Push-Location $projectDir
try { poetry run spotisync @args }
finally { Pop-Location }
}For Docker:
function spotisync {
$running = docker ps --format '{{.Names}}' | Select-String -Quiet 'spotisync'
if (-not $running) {
Write-Error "spotisync container is not running"
return
}
docker exec -it spotisync spotisync @args
}Then add to your PowerShell profile ($PROFILE):
if (Test-Path "$env:USERPROFILE\spotisync_shell.ps1") { . "$env:USERPROFILE\spotisync_shell.ps1" }Reload: . $PROFILE
- Spotify account
- Spotify Developer App credentials
- Python 3.12+
MIT License - see the LICENSE file for details.