Export, import, and manage music playlists in Plex. Works with Plex running locally or anywhere on your network.
- Requirements
- Installation & Setup
- Quick Start
- Connection Options
- Commands
- Output Files
- Common Scenarios
- Python 3.10+
- A running Plex Media Server (local or networked)
- Your Plex authentication token (or Plex installed locally for auto-detection)
1. Install dependencies:
cd plex_playlist_tools
pip install -r requirements.txt2. Create your .env file:
cp .env.example .envEdit .env and fill in your values:
| Variable | Required | Description |
|---|---|---|
PLEX_URL |
No | URL of your Plex server (default: http://localhost:32400) |
PLEX_TOKEN |
No | Your Plex auth token — auto-detected from local Plex if not set |
PLEX_LIBRARY_NAME |
No | Music library name to use — uses the first one found if blank |
OUTPUT_FILE |
No | Default export CSV filename (default: plex_music_export.csv) |
IMPORT_FILE |
No | Default import CSV filename (default: plex_music_export.csv) |
LOG_FILE |
No | Default log CSV filename (default: plex_music_log.csv) |
IMAGES_DIR |
No | Path to a directory for playlist cover images — skipped entirely if blank |
If Plex is installed on your machine, no configuration is needed — the token is auto-detected:
# See what playlists you have
python plex_playlist_tools.py list-playlists
# Export all playlists to a CSV
python plex_playlist_tools.py export --all-playlists
# Get playlist suggestions based on your library
python plex_playlist_tools.py suggest
# Create a playlist by describing it
python plex_playlist_tools.py generate "chill lo-fi for studying"If Plex is on another machine, pass the connection details:
python plex_playlist_tools.py --url http://192.168.1.50:32400 --token YOUR_TOKEN list-playlistsThe tool resolves the server URL and token using this priority order:
| Priority | Source | How to set |
|---|---|---|
| 1 (highest) | CLI flags | --url URL and/or --token TOKEN |
| 2 | .env file |
PLEX_URL=... / PLEX_TOKEN=... |
| 3 (lowest) | Auto-detected from local Plex | Plex installed on the same machine |
If no token is configured, the tool reads it automatically from the Plex Media Server Preferences.xml file. This means no setup is required if Plex is installed locally.
| OS | Preferences.xml location |
|---|---|
| Windows | %LOCALAPPDATA%\Plex Media Server\Preferences.xml |
| macOS | ~/Library/Application Support/Plex Media Server/Preferences.xml |
| Linux | /var/lib/plexmediaserver/Library/Application Support/Plex Media Server/Preferences.xml |
- Open Plex Web and sign in
- Browse to any media item, click "..." → Get Info → View XML
- In the URL that opens, copy the
X-Plex-Token=value
Full guide: https://support.plex.tv/articles/204059436-finding-an-authentication-token-x-plex-token/
The --url and --token flags come before the subcommand and override .env for that run:
python plex_playlist_tools.py --url http://192.168.1.50:32400 --token YOUR_TOKEN export --all-playlistsAll commands follow the same pattern:
python plex_playlist_tools.py [--url URL] [--token TOKEN] <command> [options]
Lists all music playlists on your Plex server with their track counts.
python plex_playlist_tools.py list-playlistsExample output:
Found 3 music playlist(s):
- Road Trip (42 tracks)
- Chill Mix (28 tracks)
- Workout (55 tracks)
Exports music data to a CSV file.
| Flag | What it exports |
|---|---|
| (no flag) | Full music library (all tracks) |
--playlist NAME |
One or more named playlists |
--all-playlists |
Every music playlist on the server |
Flags:
| Flag | Default | Description |
|---|---|---|
--output |
OUTPUT_FILE from .env |
Path for the output CSV file |
--log |
LOG_FILE from .env |
Path for the log CSV file |
--images-dir |
IMAGES_DIR from .env |
Path to a directory where cover images will be saved. Created automatically if it does not exist. |
--all-images |
off | Download every available cover per playlist into --images-dir, not just the selected one. Requires --images-dir. |
Examples:
# Export the full music library
python plex_playlist_tools.py export
# Export a single playlist
python plex_playlist_tools.py export --playlist "Road Trip"
# Export multiple playlists into one CSV
python plex_playlist_tools.py export --playlist "Road Trip" "Chill Mix" "Workout"
# Export all playlists
python plex_playlist_tools.py export --all-playlists
# Export all playlists with the currently selected cover image saved to ./covers/
python plex_playlist_tools.py export --all-playlists --images-dir ./covers
# Export all playlists with every available cover image
python plex_playlist_tools.py export --all-playlists --images-dir ./covers --all-imagesNotes:
- Playlist exports include a
Summarycolumn (the description text set on the playlist). It is restored automatically when youimportthe CSV.--images-diralone saves one file per playlist (the active cover):<dir>/<playlist name>.jpg--all-imagessaves every cover into a sub-folder:<dir>/<playlist name>/<playlist name>-1-selected.jpg,<playlist name>-2.jpg, etc. The-selectedsuffix marks the active cover.- Library exports do not support image export.
Reads a previously exported CSV and recreates the playlists in Plex. Tracks are matched first by file path, then by artist + track title. Playlist summaries stored in the Summary column are restored automatically.
Flags:
| Flag | Default | Description |
|---|---|---|
--file |
IMPORT_FILE from .env |
Path to the CSV file to import |
--log |
LOG_FILE from .env |
Path for the log CSV file |
--images-dir |
IMAGES_DIR from .env |
Path to a directory containing exported cover images to restore. Skipped if not provided. |
--mode |
append |
append: add only tracks not already in the playlist. replace: delete and recreate the playlist entirely. |
Examples:
# Import using default settings (appends new tracks to existing playlists)
python plex_playlist_tools.py import
# Import from a specific file
python plex_playlist_tools.py import --file my_playlists.csv
# Append new tracks to existing playlists (default)
python plex_playlist_tools.py import --file my_playlists.csv --mode append
# Replace existing playlists entirely
python plex_playlist_tools.py import --file my_playlists.csv --mode replace
# Import and restore cover images from a directory
python plex_playlist_tools.py import --file my_playlists.csv --images-dir ./coversTip: Only CSVs exported with
--playlistor--all-playlistscan be imported — they must have a populatedPlaylistcolumn. Library exports have a blankPlaylistcolumn and cannot be imported.
Note on
--mode:
append(default): only tracks not already in the playlist are added. Duplicates are skipped and logged. If the playlist does not exist yet it is created.replace: the existing playlist is deleted and recreated. Cover images are re-uploaded if--images-diris set.
Scans your music library and proposes playlist ideas based on genre, decade, mood, and artist. You can then choose which ones to create.
There are two modes:
- Standard (default) — fast; uses genre tags already in your Plex library, cascading from track → album → artist.
- Deep (
--deep) — thorough; queries MusicBrainz for accurate mood and genre tags. Artist MBIDs are fetched in batches of 25, then oneget_artist_by_idcall is made per artist — roughly 1 request per artist total. A library of 300 artists takes about 5–6 minutes on the first run. All results are cached locally, so subsequent runs complete in seconds.
Flags:
| Flag | Default | Description |
|---|---|---|
--min-tracks |
10 |
Minimum tracks required for a suggestion to appear |
--min-artist-tracks |
20 |
Minimum tracks by one artist for a "Best of" suggestion (requires --include-best-of) |
--limit |
25 |
Maximum number of suggestions to display |
--create-all |
off | Create every suggestion without prompting |
--deep |
off | Look up MusicBrainz tags for more accurate mood/genre suggestions |
--cache-file |
mb_cache.json |
JSON file used to cache MusicBrainz results between runs |
--reset-cache |
off | Delete the entire cache and re-fetch all artists (use after major library changes) |
--refresh-artist |
— | Re-fetch one or more specific artists without clearing the rest of the cache |
--include-best-of |
off | Also suggest "Best of <Artist>" playlists (omitted by default) |
--log |
LOG_FILE from .env |
Path for the log CSV file |
Examples:
# Standard suggestions from Plex metadata
python plex_playlist_tools.py suggest
# Deep suggestions using MusicBrainz (first run is slow; subsequent runs use cache)
python plex_playlist_tools.py suggest --deep
# Deep mode with a custom cache location
python plex_playlist_tools.py suggest --deep --cache-file /data/mb_cache.json
# Wipe the cache and re-fetch everything (e.g. after a large library overhaul)
python plex_playlist_tools.py suggest --deep --reset-cache
# Re-fetch specific artists only, leave everyone else cached
python plex_playlist_tools.py suggest --deep --refresh-artist "Pink Floyd" "David Bowie"
# Include "Best of <Artist>" suggestions
python plex_playlist_tools.py suggest --include-best-of
# Lower threshold for smaller libraries and create everything in one shot
python plex_playlist_tools.py suggest --min-tracks 5 --create-all
# Show up to 50 suggestions
python plex_playlist_tools.py suggest --limit 50Example output (standard):
Scanned 4,312 tracks across 287 artists.
47 suggestion(s) found. Showing top 25:
[ 1] [mood] Chill Mix 892 tracks — Ambient, Acoustic, Lo-Fi…
[ 2] [mood] Rock Mix 754 tracks — Rock, Classic Rock, Alternative…
[ 3] [decade] 90s Hits 612 tracks from 1990–1999
[ 4] [genre] Rock 521 tracks
[ 5] [decade + genre] 90s Rock 318 tracks from 1990–1999
...
Enter number(s) to create (e.g. 1 or 1,3,5 or all), or 'q' to quit:
> 1,3
Created 'Chill Mix' (892 tracks)
Created '90s Hits' (612 tracks)
Created 2 playlist(s).
Example output (deep mode):
Scanned 4,312 tracks across 287 artists.
Enriching tracks for 287 artist(s) via MusicBrainz (4 workers, 1 request/artist after batch MBID lookup).
Estimated time: ~299s | Cache: mb_cache.json
[287/287] artists enriched
Enrichment complete. Cache saved to mb_cache.json
63 suggestion(s) found. Showing top 25:
[ 1] [mood] melancholic 412 tracks — melancholic, sad, emotional…
[ 2] [mood] Chill Mix 387 tracks — chill, relaxing, mellow…
[ 3] [genre] indie rock 341 tracks
[ 4] [decade] 90s Hits 298 tracks from 1990–1999
...
Note (deep mode): Artist MBIDs are pre-fetched in batches of 25 (one Lucene OR query per batch), then a single
get_artist_by_idcall retrieves genre tags for each artist — roughly 1 request per artist rather than paginated per-recording browses. Uses 4 concurrent workers; the 1 req/sec MusicBrainz rate limit is enforced globally across all workers without holding locks during sleeps. A 20-second per-request timeout prevents slow responses from stalling a run. Progress is saved every 40 artists — if you interrupt and re-run, already-processed artists are skipped instantly from the cache. New library tracks are picked up automatically on the next run. Use--reset-cacheto re-fetch everything or--refresh-artistto re-fetch specific artists.
Note (standard mode): Suggestions are generated from genre tags, release years, and artist metadata already in your Plex library. If your library has limited metadata, try lowering
--min-tracks.
Creates a playlist by describing what you want in plain language. The tool searches your library for matching tracks using genre tags, mood keywords, decade keywords, and artist names.
Usage:
python plex_playlist_tools.py generate "DESCRIPTION"Flags:
| Flag | Default | Description |
|---|---|---|
--name |
— | Playlist name (defaults to your description) |
--min-tracks |
5 |
Warn if fewer than this many tracks are found |
--yes / -y |
off | Skip confirmation and create the playlist immediately |
--log |
LOG_FILE from .env |
Path for the log CSV file |
Examples:
# Create a lo-fi study playlist
python plex_playlist_tools.py generate "chill lo-fi for studying"
# Create a 90s rock playlist with a custom name
python plex_playlist_tools.py generate "90s rock" --name "Throwback Rock"
# Create a workout playlist without confirmation
python plex_playlist_tools.py generate "high energy workout" --yes
# Create a playlist for a specific artist
python plex_playlist_tools.py generate "Pink Floyd"Example output:
Searching for: "chill lo-fi for studying"
Matched on:
· keyword: 'chill'
· keyword: 'lofi'
· keyword: 'study'
Found 143 track(s) → playlist name: 'Chill Lo-Fi For Studying'
Create 'Chill Lo-Fi For Studying' with 143 tracks? [y/N] y
Created playlist 'Chill Lo-Fi For Studying' with 143 tracks.
How matching works:
- Decade keywords (
90s,eighties,2000s, etc.)- Mood / genre keywords from a built-in map (
chill,lofi,workout,jazz,rock, etc.)- Direct word matching against your library's genre tags
- Artist name matching as a final fallback
Results depend on how well your library is tagged in Plex. If nothing is found, run
suggestto see what your library can support.
Scans one or more playlists for duplicate tracks and removes them. A duplicate is any track that appears more than once in the same playlist (matched by Plex internal ID, so identical files are always caught).
Flags:
| Flag | Default | Description |
|---|---|---|
--playlist NAME |
— | One or more playlist names to deduplicate (required unless --all-playlists) |
--all-playlists |
off | Deduplicate every music playlist |
--yes / -y |
off | Remove duplicates without confirmation |
--log |
LOG_FILE from .env |
Path for the log CSV file |
Examples:
# Check a single playlist for duplicates (prompts before removing)
python plex_playlist_tools.py dedupe --playlist "Road Trip"
# Check multiple playlists
python plex_playlist_tools.py dedupe --playlist "Road Trip" "Chill Mix"
# Deduplicate all playlists without prompting
python plex_playlist_tools.py dedupe --all-playlists --yesExample output:
'Road Trip' — 3 duplicate(s) in 45 tracks:
· Tom Petty – American Girl
· Fleetwood Mac – Go Your Own Way
· Eagles – Hotel California
Remove 3 duplicate(s) from 'Road Trip'? [y/N] y
Removed 3 duplicate(s) from 'Road Trip'.
Removed 3 duplicate track(s) total.
Creates a new playlist that is a shuffled copy of an existing one. Useful for devices or apps that play tracks in the order they appear in the playlist.
Usage:
python plex_playlist_tools.py shuffle "PLAYLIST NAME"Flags:
| Flag | Default | Description |
|---|---|---|
--name |
<name> (Shuffled) |
Name for the new shuffled playlist |
--seed |
— | Integer seed for a reproducible shuffle |
--yes / -y |
off | Skip confirmation |
--log |
LOG_FILE from .env |
Path for the log CSV file |
Examples:
# Shuffle a playlist (creates "Road Trip (Shuffled)")
python plex_playlist_tools.py shuffle "Road Trip"
# Shuffle with a custom name
python plex_playlist_tools.py shuffle "Road Trip" --name "Road Trip Randomized"
# Reproducible shuffle — same seed always produces the same order
python plex_playlist_tools.py shuffle "Road Trip" --seed 42
# Skip confirmation
python plex_playlist_tools.py shuffle "Road Trip" --yesNote: The original playlist is not modified. A new playlist is created (or replaced if one with the same name already exists).
Copies playlist(s) from one Plex server to another. Tracks are matched on the destination by file path first, then by artist + title.
The source server is the one specified by the global --url / --token flags (or .env). The destination is specified with --dest-url and --dest-token.
Flags:
| Flag | Default | Description |
|---|---|---|
--playlist NAME |
— | One or more playlist names to sync (required unless --all-playlists) |
--all-playlists |
off | Sync every music playlist from the source |
--dest-url |
(required) | Destination Plex server URL |
--dest-token |
(required) | Destination Plex auth token |
--dest-library |
— | Music library name on the destination (uses first found if blank) |
--mode |
replace |
replace: recreate the playlist. append: add only new tracks. |
--log |
LOG_FILE from .env |
Path for the log CSV file |
Examples:
# Sync all playlists from one server to another
python plex_playlist_tools.py --url http://192.168.1.10:32400 --token TOKEN_A \
sync --all-playlists \
--dest-url http://192.168.1.20:32400 --dest-token TOKEN_B
# Sync a specific playlist
python plex_playlist_tools.py --url http://192.168.1.10:32400 --token TOKEN_A \
sync --playlist "Road Trip" \
--dest-url http://192.168.1.20:32400 --dest-token TOKEN_B
# Append new tracks instead of replacing
python plex_playlist_tools.py --url http://192.168.1.10:32400 --token TOKEN_A \
sync --all-playlists --mode append \
--dest-url http://192.168.1.20:32400 --dest-token TOKEN_BNote: Tracks that cannot be matched on the destination are skipped and logged. The quality of matching depends on both libraries having the same file paths or consistent artist/title metadata.
Renames a playlist directly on the Plex server.
Usage:
python plex_playlist_tools.py rename "OLD NAME" "NEW NAME"Examples:
# Rename a playlist
python plex_playlist_tools.py rename "Road Trip" "Summer Road Trip 2024"
# Works with remote servers too
python plex_playlist_tools.py --url http://192.168.1.50:32400 --token YOUR_TOKEN \
rename "Chill Mix" "Evening Chill"Combines two or more playlists into a single new playlist. Duplicate tracks are removed by default.
Usage:
python plex_playlist_tools.py merge "PLAYLIST 1" "PLAYLIST 2" ... --name "MERGED NAME"Flags:
| Flag | Default | Description |
|---|---|---|
--name |
(required) | Name for the merged playlist |
--allow-duplicates |
off | Keep duplicate tracks from different source playlists |
--yes / -y |
off | Skip confirmation |
--log |
LOG_FILE from .env |
Path for the log CSV file |
Examples:
# Merge two playlists
python plex_playlist_tools.py merge "Morning Vibes" "Afternoon Groove" --name "Day Mix"
# Merge three playlists
python plex_playlist_tools.py merge "Rock Classics" "90s Rock" "Indie Rock" --name "All Rock"
# Merge and allow duplicate tracks
python plex_playlist_tools.py merge "Party Mix A" "Party Mix B" --name "Big Party Mix" --allow-duplicates
# Skip confirmation
python plex_playlist_tools.py merge "Morning Vibes" "Evening Chill" --name "Full Day Mix" --yesExample output:
Merging 2 playlist(s) → 'Day Mix'
Sources : Morning Vibes, Afternoon Groove
Tracks : 84 (12 duplicate(s) removed)
Create 'Day Mix' with 84 tracks? [y/N] y
Created 'Day Mix' with 84 tracks.
Note: If a playlist named
--namealready exists it will be replaced.
Produced by the export command.
| Column | Description |
|---|---|
| Playlist | Playlist name (blank for library exports) |
| Summary | Playlist description/summary (blank for library exports) |
| Artist | Artist name |
| Album | Album title |
| Year | Release year |
| Track Number | Track position within the album |
| Track Title | Name of the track |
| Duration (s) | Track duration in seconds |
| Genre | Genre tags (comma-separated) |
| File Path | Full path to the audio file on the Plex server |
Example:
Playlist,Artist,Album,Year,Track Number,Track Title,Duration (s),Genre,File Path
Road Trip,Tom Petty,Greatest Hits,1993,1,American Girl,214,Rock,/music/Tom Petty/01 American Girl.flac
Road Trip,Fleetwood Mac,Rumours,1977,1,Second Hand News,143,Rock,/music/Fleetwood Mac/01 Second Hand News.flac
Produced by all commands that modify playlists. Each run appends to the log so you keep a full history.
| Column | Description |
|---|---|
| Timestamp | Date and time (ISO 8601) |
| Operation | export, import, generate, dedupe, shuffle, sync, merge, export_image, or import_image |
| Playlist | Playlist name |
| Artist | Artist name (blank for image, generate, shuffle, and merge rows) |
| Album | Album title (blank for image, generate, shuffle, and merge rows) |
| Track Title | Track name (blank for image, generate, shuffle, and merge rows) |
| Status | success, error, or skipped |
| Message | Details — file path on image success, reason on error or skip |
python plex_playlist_tools.py list-playlists
python plex_playlist_tools.py suggest# Export all playlists (includes playlist summaries and file paths)
python plex_playlist_tools.py export --all-playlists --output plex_backup.csv
# Restore on the same or a different Plex server
python plex_playlist_tools.py import --file plex_backup.csv --mode replace
# Include cover images in the backup and restore them too
python plex_playlist_tools.py export --all-playlists --output plex_backup.csv --images-dir ./covers
python plex_playlist_tools.py import --file plex_backup.csv --images-dir ./covers --mode replaceImport matches tracks by file path first, then falls back to artist + title — so it works even after a Plex library re-scan that changes internal IDs. Playlist summaries are preserved automatically.
# Browse suggestions and pick what to create (uses Plex genre tags)
python plex_playlist_tools.py suggest
# Create everything at once without prompting
python plex_playlist_tools.py suggest --create-all
# Deep mode: enrich tags from MusicBrainz first (slow on first run, cached after)
python plex_playlist_tools.py suggest --deep
# Include "Best of <Artist>" suggestions
python plex_playlist_tools.py suggest --include-best-ofpython plex_playlist_tools.py generate "relaxing jazz for a dinner party"
python plex_playlist_tools.py generate "80s synthwave" --name "Retro Synth"
python plex_playlist_tools.py generate "upbeat pop hits" --yes# Re-import and append only new tracks (default mode)
python plex_playlist_tools.py import --file updated_export.csvpython plex_playlist_tools.py dedupe --all-playlists --yespython plex_playlist_tools.py shuffle "Road Trip" --name "Road Trip (Party Mix)" --yespython plex_playlist_tools.py merge "Rock Classics" "90s Rock" "Indie Rock" --name "All Rock" --yespython plex_playlist_tools.py --url http://192.168.1.10:32400 --token TOKEN_A \
sync --all-playlists \
--dest-url http://192.168.1.20:32400 --dest-token TOKEN_Bpython plex_playlist_tools.py rename "Old Name" "New Name"python plex_playlist_tools.py export --playlist "Road Trip" --images-dir ./covers --all-imagesSaves all covers into:
covers/
Road Trip/
Road Trip-1-selected.jpg ← currently active cover
Road Trip-2.jpg
Road Trip-3.jpg
python plex_playlist_tools.py --url http://192.168.1.50:32400 --token YOUR_TOKEN export --all-playlists