Skip to content

Config

Config is TOML. OAuth credentials live in private auth files under the app config directory, not in an OS keyring. On Unix, spotuify writes its config file with mode 0600, the auth directory with mode 0700, and auth files with mode 0600.

Terminal window
spotuify config path
SPOTUIFY_CONFIG=/tmp/spotuify.toml spotuify config path

These keys are accepted by spotuify config get and spotuify config set.

Key Type Default Notes
client_id string required Spotify Developer app client id for the default PKCE flow
client_secret string none optional for PKCE; config get redacts it unless --reveal-secret is passed
redirect_uri string http://127.0.0.1:8888/callback must match the Spotify app settings
player.backend enum embedded only embedded (in-process librespot); Spotifyd/Connect-only backends were removed
player.bitrate number 320 96, 160, or 320
player.device_name string none preferred embedded/connect device name
player.audio_output_device string system default local audio output the embedded player renders to; match a name from spotuify audio-outputs
player.normalization bool false player normalization
player.audio_cache_mib number 0 embedded playback cache size
player.pulse_props bool true Linux Pulse/PipeWire app props
player.event_hook string none legacy alias for analytics.hook_command
analytics.hook_command string none shell hook command for qualified listens
analytics.hook_timeout_ms number 5000 hard timeout for the hook command
cache.cover_cache_mb number 200 cover-art cache cap
cache.cover_cache_ttl_days number 30 cover-art TTL
notifications.enabled bool false master switch for desktop notifications (needs the notifications build feature)
notifications.summary string {track} summary template ({track} {artist} {album} tokens)
notifications.body string {artist} - {album} body template
notifications.on_track_change bool true notify when the playing track changes
notifications.on_pause bool false notify on pause
notifications.on_resume bool false notify on resume
notifications.on_skip bool false notify on next/previous skips
notifications.on_error bool true notify on auth errors (deduped)
discord.enabled bool false Discord Rich Presence (needs the discord-rpc build feature + Discord running)
discord.application_id string none Discord application id for Rich Presence
Terminal window
spotuify config get player.bitrate
spotuify config set player.bitrate 320
spotuify config get client_secret
spotuify config get client_secret --reveal-secret

The notifications.summary / notifications.body templates expand these tokens from the current track: {track}, {artist}, {album}, {duration} (m:ss), and {progress} (m:ss). Defaults are {track} / {artist} - {album}.

Some config is loaded from TOML but not yet wired through config set.

[analytics]
store_raw_queries = true
retention_progress_days = 90
retention_events_days = 365
retention_operations_days = 90
daily_rollup_hour = 3
hook_command = "/Users/me/bin/spotuify-listen-hook"
hook_timeout_ms = 5000
allow_file_credentials = false
lastfm_api_key = "lastfm-api-key"
lastfm_user = "your-lastfm-user"
[viz]
enabled = true
source = "auto"
target_fps = 30
smoothing = 0.5
noise_gate = 0.005
color_scheme = "spotify-green"
style = "bars"
[tui]
theme = "terminal-default"

The visualizer ships on by default. Set enabled = false to opt out. It animates from the embedded librespot sink tap; when no audio is playing the spectrum draws a flat baseline. Toggle it off if you want the player to use that vertical space for queue items instead.

spotuify viz enable / viz disable and spotuify viz source <kind> write enabled and source back to this file, so the choice survives a daemon restart. The runtime effect is immediate either way.

style picks the renderer. spotuify viz styles prints the full list with a one-line description each; spotuify viz style <name> sets it, and next / prev cycle. In the TUI, ctrl+v opens a picker that previews each style live, and V blows the visualizer up to fill the terminal. Available styles:

Style What it draws
bars Smooth fractional block bars, one per band (default).
bars-dot Bars stippled with Braille dots.
bars-outline Only the top edge of each bar, as a line graph.
bricks Half-height blocks stacked with gaps.
columns Thin single-column bars interpolated between bands.
classic-peak Thin columns under falling peak caps.
classic-led Winamp-style LED matrix with held peak caps.
mirror Braille bars mirrored about a horizontal axis.
scatter Twinkling Braille particle field.
rain Falling streaks confined to the bar shapes.
matrix Katakana digital rain, density driven by energy.
flame Doom-fire heat field fed by the spectrum.
retro Synthwave sun, horizon wave, and scrolling grid.
pulse Pulsating Braille ellipse with shockwave rings.
wave Braille oscilloscope tracing the raw waveform.
scope Lissajous XY scope from a phase-delayed waveform.
heartbeat ECG monitor trace over a dashed baseline.
sakura Cherry blossom petals drifting down on the breeze.
firework Launching bursts that explode and fall under gravity.
bubbles Hollow bubbles rising and popping at the surface.
terrain Side-on mountain range scrolling in from the right.
firefly Fireflies blinking over a grass silhouette.
mosaic Fixed heatmap tiles igniting and fading in place.
sand Falling-sand automaton that bass kicks blow apart.
geyser Particle fountain erupting on bass transients.
butterfly Symmetric ink-blot wings mirrored about the centre.
binary Columns of 0s and 1s streaming at per-band speeds.
ascii Thin columns drawn with shade blocks (█ ▓ ▒ ░).

wave, scope, and heartbeat trace the raw waveform rather than the 12-band spectrum. They need a daemon new enough to send one; against an older daemon they draw a resting trace instead of failing.

Style names are matched case-insensitively and trimmed, and spotuify config set viz.style writes the canonical spelling.

color_scheme still applies to every style. Under NO_COLOR the styles keep their glyphs but drop colour; bars additionally falls back to #.

source (auto, sink, loopback, none) and color_scheme (spotify-green, rainbow, monochrome) are matched case-insensitively and trimmed, and spotuify config set writes the canonical spelling. That is the same rule viz.style and tui.theme follow.

tui.theme picks the terminal colour theme. terminal-default (the default) keeps the palette spotuify ships with. spotuify theme list prints the built-ins plus anything in <config_dir>/themes; see Themes for the file format. Names are matched case-insensitively and trimmed, and spotuify config set tui.theme writes the canonical spelling. Setting a name that does not resolve to a built-in or an existing file is rejected. Editing the key by hand takes effect on spotuify reload, which broadcasts the new colours to running clients.

analytics.lastfm_api_key and analytics.lastfm_user are defaults for historical Last.fm import. CLI flags override them:

Terminal window
spotuify analytics import lastfm --user your-lastfm-user --from 2024-01-01

The default auth path is dev-app PKCE. Put client_id in config or set SPOTUIFY_CLIENT_ID before login. First-party/keymaster auth is opt-in for experiments with SPOTUIFY_USE_FIRST_PARTY=1.

Terminal window
SPOTUIFY_CLIENT_ID=... spotuify login
SPOTUIFY_CLIENT_SECRET=... spotuify login
SPOTUIFY_REDIRECT_URI=http://127.0.0.1:8888/callback spotuify login
SPOTUIFY_USE_FIRST_PARTY=1 spotuify login

Last.fm historical import also reads environment defaults:

Terminal window
SPOTUIFY_LASTFM_API_KEY=lastfm-api-key \
SPOTUIFY_LASTFM_USER=your-lastfm-user \
spotuify analytics import lastfm --from 2024-01-01 --format json

For local development and tests:

Terminal window
# Run the whole stack against fake Spotify data; never touches live
# Spotify auth. Honored by the CLI, daemon, and TUI uniformly.
SPOTUIFY_FAKE_SPOTIFY=1 spotuify

The old proactive scope-drift credential read no longer runs at daemon startup. Scope checks now reuse the first real token read from the auth file.

Media controls (MPRIS on Linux, Now Playing on macOS, SMTC on Windows) are on by default. Set SPOTUIFY_NO_MEDIA_CONTROLS=1 before starting the daemon to turn them off entirely; on Windows this also skips the hidden-window driver.

Terminal window
SPOTUIFY_NO_MEDIA_CONTROLS=1 spotuify daemon restart

On startup the TUI asks the terminal which image protocol it speaks, so album art can use kitty graphics or sixel where they exist. A terminal that never answers leaves the query’s reader thread blocked on stdin; it then swallows the first keystroke and hands back the terminal settings from before the TUI started, which looks like a frozen TUI. spotuify skips the query where no answer is likely: piped stdin, a tmux window nobody is watching, GNU screen. Set SPOTUIFY_NO_TERMINAL_QUERY=1 to skip it everywhere; album art falls back to half-blocks.

Detection is bounded at 500 ms end to end. If it trips, spotuify says so and exits rather than hanging; a wedged tmux server is the usual cause, and tmux kill-server clears it.

Terminal window
SPOTUIFY_NO_TERMINAL_QUERY=1 spotuify
Terminal window
spotuify -o player.bitrate=160 play "ambient"
spotuify -o player.normalization=true play "ambient"

Overrides apply only to that command.