App-specific color overrides on top of the active Textual theme
auth.json
YouTube Music credentials (auto-generated by ytm setup)
account.json
Which account auth.json belongs to (written by ytm setup; required for automatic session renewal)
stream_cookies.txt
Session cookies for streaming (written by ytm setup; used only with use_session_cookies)
Open the config directory in your editor:
ytm config
config.toml
Every section is optional — anything you don't set falls back to defaults.
[general]
[general]startup_page = "library" # library, search, browseplayback_bar_position = "bottom" # "bottom" or "top"brand_account_id = "" # YouTube Brand Account ID (21-digit; find at myaccount.google.com/brandaccounts)check_for_updates = true # check PyPI once per 24h, surface a one-time toast on new version
[playback]
[playback]audio_quality = "high" # high, medium, lowprefer_audio = true # prefer audio-only streams over video (audio uses less bandwidth)default_volume = 80 # 0-100autoplay = true # auto-play next on track endgapless = true # preload the next track for gapless transitionsseek_step = 5 # seconds per +/- seekapi_timeout = 15 # seconds for ytmusicapi calls before failoverresume_on_launch = true # restore last-playing track + position on app start; press space to continuehistory_min_listen_seconds = 5 # seconds before a track counts as a play instead of a skipsync_history_to_ytmusic = true # report TUI plays back to your YT Music account history
resume_on_launch (added v1.7.0) stages the last-playing track + position into the playback bar on startup. Press space to continue from where you were. Set to false to start fresh every time.
history_min_listen_seconds controls the threshold for local history and YT Music account reporting. Tracks listened to for this many seconds or less are treated as skips. Set to 0 to count any playback of at least one full second (sub-second listens still count as skips). Values are clamped to 0..3600.
sync_history_to_ytmusic reports tracks you play in the TUI back to your YouTube Music account history (via ytmusicapi's add_history_item), so they show up in your history and feed recommendations like any other client. It uses history_min_listen_seconds as the reporting threshold. Set to false to keep TUI listening off your account.
Recently Played tabs
g r opens Recently Played with three tabs; a line under the tab row says what the active one shows.
All (default) — local history first, followed by additional account history. One row per track: a track played both here and elsewhere keeps its local position and time, with artist, album and artwork details filled in from the account row. It is two groups, not one timeline — the account feed only says roughly when a track was played, so a phone play from an hour ago can sit below an older play from this app. Up to 100 local rows plus up to 100 further account rows.
Local — tracks played in this app, most recent first, up to 100.
YT Music — your account's complete play history from any device, in YouTube Music's order, up to 100. Needs a signed-in session.
If one source can't be loaded, All shows the other and says so in the footer. Re-selecting the active tab refreshes it; refreshing All refetches both sources. A play the account accepts (with sync_history_to_ytmusic on) appears on the YT Music and All tabs right away.
[cache]
[cache]enabled = truemax_size_mb = 1024 # 1GB default LRU audio cacheprefetch_next = true # resolve next track's stream URL in background for instant skiplocation = "" # blank = default cache dir; set a path to override
Tracks saved with Download for Offline live in this cache and count toward max_size_mb. When the limit is reached, the least recently accessed files are removed — downloads included. A download that doesn't fit within the limit on its own is reported as not retained. A download interrupted by the app being killed starts again from the beginning the next time you download that track.
[yt_dlp]
[yt_dlp]cookies_file = "" # Optional: path to yt-dlp Netscape cookies.txtuse_session_cookies = false # Stream with the session from `ytm setup` (see below)ca_bundle = "" # Optional: path to a custom CA cert bundle for SSL-inspecting # corporate proxies (Zscaler, Netskope, etc.)remote_components = "" # Optional: ejs:npm/ejs:github (enables remote JS component downloads)js_runtimes = "" # Optional: bun, bun:/path/to/bun, node, quickjs, etc.
Streaming is anonymous by default: yt-dlp resolves stream URLs without any cookies, which works
without extra software. ytm setup (and the automatic session refresh) also writes the
youtube.com/google.com cookies of your session to stream_cookies.txt; with
use_session_cookies = true the resolver loads that file, so tracks that need your account
(Premium-only, age-restricted) resolve as you.
That mode relies on yt-dlp's JavaScript challenge solver, because YouTube's cookie-aware clients
need it. Either install the solver locally (pip install "yt-dlp[default]" pulls in
yt-dlp-ejs) or let yt-dlp download it with remote_components = "ejs:github", and have a
supported JavaScript runtime installed (deno is used automatically; for node, bun or
quickjs set js_runtimes). Without a working solver, authenticated playback may fail while
use_session_cookies is on; anonymous streaming is unaffected. yt-dlp never writes
stream_cookies.txt back; re-run ytm setup to refresh it. An explicit cookies_file takes
precedence and is used the way yt-dlp normally uses it.
[search]
[search]default_mode = "music" # "music" (songs only) or "all" (all result types)max_history = 500 # number of past search queries to rememberpredictive = true # show search suggestions as you type
[ui]
[ui]theme = "ytm-dark" # startup default Textual theme; runtime changes are saved in session.jsonalbum_art = true # show colored half-block album art in playback barprogress_style = "block" # block or linesidebar_width = 30col_index = 4 # 0 = auto-fill widthcol_title = 0 # 0 = auto-fillcol_artist = 0 # 0 = auto-fillcol_album = 0 # 0 = auto-fillcol_duration = 8bidi_mode = "auto" # auto, reorder, passthrough — RTL text handlingregion = "ZZ" # ISO 3166-1 alpha-2 (or "ZZ" = Global, default) — Browse → Charts. 68 regions selectable; locale-style codes like "ES-ES" auto-normalise to "ES".home_shelves = 3 # number of recommendation shelves on Browse → For You (1–25); # the Playlists tab always reads up to 25show_selection_info = true # show focused-item full name in the row above the playback barsidebar_overflow = "truncate" # "truncate" (1-row + ellipsis) or "wrap" (multi-line names)show_queue_source = true # show "Generated from: …" header on radio/discovery queues
theme is the startup default. Changing theme from Textual's command palette (Ctrl+P → Theme) updates the current session and is saved in session.json, not config.toml. To make the active theme the new default, run Ctrl+P → Set Current Theme as Default.
Per-playlist Shuffle lock state (set via the Shuffle lock toggle in the
Library page playlist header) is persisted separately to
~/.config/ytm-player/shuffle_prefs.json. There's nothing to configure
in config.toml for it.
[notifications]
[notifications]enabled = truetimeout_seconds = 5format = "{title} — {artist}" # template for the now-playing notification body
[mpris]
[mpris]enabled = true
[discord]
[discord]enabled = false # requires `pip install ytm-player[discord]`client_id = "" # blank uses the bundled app; set your own # Discord application ID to publish under it
[logging]level = "WARNING" # DEBUG, INFO, WARNING, ERROR, CRITICALmax_bytes = 5242880 # 5 MB per log file before rotationbackup_count = 3 # number of rotated logs to keepkeep_crashes = 10 # max number of crash files to retain in crashes/
theme.toml
Base colors (primary, background, etc.) come from the active Textual theme. Choose the startup default with [ui] theme, switch the current session with Ctrl+P → Theme, and use theme.toml for app-specific color overrides:
[colors]playback_bar_bg = "#1a1a1a"selected_item = "#2a2a2a"progress_filled = "#ff0000"progress_empty = "#555555"lyrics_played = "#999999"lyrics_current = "#ff4e45" # defaults to the theme accent if unsetlyrics_upcoming = "#aaaaaa"active_tab = "#ffffff"inactive_tab = "#999999"
The lyrics_current color falls back to the active theme's accent (and then to #ff4e45 red as the absolute last-resort default). Override only if you want something different from your theme's accent.
keymap.toml
For custom keybinding overrides, see docs/keybindings.md for the full key list and the customization syntax.
Available filters: songs, videos, albums, artists, playlists, community_playlists, featured_playlists.
Stats and history
ytm stats # Listening stats summaryytm stats --json # Machine-readableytm history # Recent play historyytm history search # Recent search history
Cache management
ytm cache status # Cache size + entry countytm cache clear # Wipe all cached audio
Playback control (IPC, requires TUI running)
ytm play # Resume playbackytm pause # Pause playbackytm next # Skip to next trackytm prev # Previous trackytm seek +10 # Seek forward 10 secondsytm seek -5 # Seek backward 5 secondsytm seek 1:30 # Seek to 1:30 (m:ss or h:mm:ss)
Like / dislike (IPC)
ytm like # Like current trackytm dislike # Dislike current trackytm unlike # Remove like/dislike (sets to INDIFFERENT)
Status (IPC)
ytm now # Current track info (JSON)ytm status # Player status (JSON)ytm queue # Queue contents (JSON)ytm queue add VIDEO_ID # Add track by video IDytm queue clear # Clear queue
Reads track names + artists from the Spotify playlist.
→
2
Match
Searches YouTube Music with fuzzy matching (60% title + 40% artist weighted score).
→
3
Resolve
Tracks scoring 85%+ are auto-matched. Lower scores prompt you to pick from candidates or skip.
→
4
Create
Creates a new private playlist on your YouTube Music account with all matched tracks.
Two import modes
Single mode
Up to ~100 tracks. Best for most playlists.
How: paste one Spotify playlist URL.
Multi mode
100+ tracks. For large playlists, the importer splits the URL list across multiple calls.
How: enter a name + number of parts, then paste a URL for each part.
Run from the TUI or the CLI
From the TUI
Click Import in the footer (or press the import button). A popup lets you paste URLs, choose single or multi mode, and watch import progress in real time.
Interactive flow: fetches tracks, shows match results, lets you resolve ambiguous matches, names the playlist, then creates it.
Extraction methods
1. Spotify Web API
Full pagination, handles any playlist size. Requires a free Spotify Developer app (you set up client_id + client_secret in ~/.config/ytm-player/spotify.json).
→ falls back to →
2. Scraper fallback
No credentials needed. Limited to ~100 tracks. Used automatically if Spotify API credentials aren't configured.
Try the parser
Paste a Spotify playlist URL — see how the importer extracts the playlist ID locally (no network call):