Spotify

Search, play and queue Spotify from the keyboard

palupdated 2026-09-29MediaIn pal's registry󱔓Menu bar: Spotify

Open in pal

In pal's registry: the button shows it in pal, which asks before installing; the command does the same from a terminal. This page can't see what your pal has installed.

Spotify: The lyrics view: the cover, the progress, the line playing bright among the lines around it
The lyrics view: the cover, the progress, the line playing bright among the lines around it
Spotify: Search: tracks, artists, albums, playlists and podcasts as sections
Search: tracks, artists, albums, playlists and podcasts as sections
Spotify: Playlists: yours and the followed ones; cmd+enter lists a playlist's tracks
Playlists: yours and the followed ones; cmd+enter lists a playlist's tracks
Spotify: Library: Liked Songs newest first, the other filters on Tab
Library: Liked Songs newest first, the other filters on Tab
Spotify: The queue: what plays now, what comes next, Enter skips to a row
The queue: what plays now, what comes next, Enter skips to a row
1 of 5

What it does

Search, play, queue, like and add to playlists on Spotify from the keyboard, with lyrics that follow the song and a bar item that shows the line playing.

  • Search tracks, artists, albums, playlists and podcasts as you type; enter plays, cmd+enter queues
  • Like with cmd+l, add to a playlist with cmd+p, open in Spotify with cmd+o
  • Your playlists, liked songs, recently played, top tracks, devices and the queue
  • Synced lyrics around the line playing, with the cover, progress and playback on keys
  • Play, pause, skip and like from the root, plus a row for each pinned playlist
  • A bar item with the track or the lyric line playing

Palettes 7

  • Lyricsnow-playingview

    The track playing with its cover, a ticking progress bar, the state, and lrclib's synced lyrics around the line playing; follows the song while open.

    • spaceor↵Play or pause
    • ←or→Seek 10 s back or forward
    • ↑or↓Volume up or down
    • LLike or unlike
    • SShuffle on or off
    • RRepeat: all, one, off
    • QThe queue
    • DDevices
    • ⌘→or⌘←Next or previous track
    • ⌘CCopy the line playing (the track without lyrics)
    • ⌘OOpen in Spotify
    • FSearch lrclib when it has no lyrics
  • Search Spotifysearchinput

    Tracks, artists, albums, playlists, podcasts and episodes as sections with their cover art.

    • ↵Play
    • ⌘↵Add to queue (a track); show tracks (a playlist or album)
    • ⌘LLike or unlike a track
    • ⌘PAdd a track to the playlist picked in the bar's field
    • ⌘SPlay a playlist shuffled
    • ⌘OOpen in Spotify
    • ⌘CCopy link
  • Playlistsplaylistslistrefreshes every 5 min

    Your playlists and the ones you follow; a drill-in lists the tracks and plays from one.

    • ↵Play
    • ⌘↵Show tracks (a playlist); add to queue (a track of one)
    • ⌘SPlay shuffled
    • ⌘LLike or unlike a track of a listed playlist
    • ⌘PAdd a listed track to the playlist picked in the bar's field
    • ⌘OOpen in Spotify
    • ⌘CCopy link
  • Librarylibrarylistrefreshes every 5 min

    Liked Songs newest first, recently played, and the top tracks and artists of the last weeks, as filters.

    • ⇥Liked Songs, Recently played, Top tracks, Top artists
    • ↵Play
    • ⌘↵Add to queue
    • ⌘LLike or unlike
    • ⌘PAdd to the playlist picked in the bar's field
    • ⌘OOpen in Spotify
    • ⌘CCopy link
  • Spotify Devicesdeviceslive

    Every device Spotify is open on: transfer playback to one, and the volume of the active one (up, down, mute, or a level typed in the bar).

    • ↵Play here (a device); volume up, down, mute (the volume rows); on Set volume…, the level typed in the bar's field
    • ⌘↵Transfer without playing
    • ⌘↑or⌘↓Volume of the active device
  • Queuequeuelive

    What plays now and what comes next; skip to a row.

    • ↵Skip to the row (play or pause on the first)
    • ⌘LLike or unlike
    • ⌘PAdd to the playlist picked in the bar's field
    • ⌘OOpen in Spotify
    • ⌘CCopy link
  • Spotifycommandslist

    Play or pause, next, previous, like, lyrics, sign out, and a Play row per pinned playlist, all root results.

    • ↵Run
    • ⌘SPlay a pinned playlist shuffled
    • ⌘OOpen a pinned playlist in Spotify

Actions

  • PlayA track, or a playlist, album, artist or show as the context.↵
  • Add to queue⌘↵
  • Show tracksA playlist's or album's tracks as a level; Enter plays from there.⌘↵
  • Play shuffled⌘S
  • LikeAdds to or removes from Liked Songs.⌘L
  • Add to playlistOn any track row: the playlist (yours or a collaborative one) picked in the bar's field; a form with the same select from a hotkey.⌘P
  • Open in Spotify⌘O
  • Copy link⌘C
  • Play or pauseIn the lyrics view.space
  • Seek back 10 s←
  • Seek forward 10 s→
  • Volume up↑
  • Volume down↓
  • Shuffle onS
  • Repeat allThen Repeat one, then off.R
  • QueueQ
  • DevicesD
  • Copy current line⌘C
  • Search on lrclibWhen lrclib has no lyrics for the track.F
  • Play hereTransfers playback to the device.↵
  • Transfer without playing⌘↵
  • Set volumeIn Devices: the level (0 to 100) typed in the bar, on the active device.↵
  • Skip to itIn the queue: one Next per row ahead.↵
  • Sign in to SpotifyOpens the browser; pal listens on 127.0.0.1 for the redirect.
  • Sign outForgets the tokens.

Settings

Extension, [extensions.spotify]

client_id
empty text

Your own Spotify app's client id (README: create one at developer.spotify.com with the redirect http://127.0.0.1:27182/callback). No secret is needed: pal signs in with PKCE.

redirect_port
27182 number

The port pal listens on for the sign-in redirect; must match the redirect URI registered on the app.

pinned
none list

Each one is a "Play <name>" row at the root.

Defaults shown. Change them in pal's settings window or in the config file.

The README

Everything Spotify does, key by key, from the extension's own README.md

Spotify

Spotify from the panel over the Web API: search as you type, play, queue, like and add to a playlist; your playlists and library; the devices and the queue; a lyrics view that follows the song; a bar item with the line playing. On sketchybar, scrolling that item skips next/previous like the existing lirik item. It needs a Spotify app of your own (two minutes at developer.spotify.com, below) and signs in with PKCE, so there is no secret anywhere.

Palettes

palette id kind what Enter does
Lyrics spotify-now-playing view play or pause; the whole transport on keys
Search Spotify spotify-search input plays the row (a track, or a playlist, album, artist or show as the context)
Playlists spotify-playlists indexed, 5 min plays the playlist; cmd+enter lists its tracks
Library spotify-library indexed, 5 min, filters plays the track (or the artist)
Spotify Devices spotify-devices live transfers playback to the device; the volume rows act on the active one, Set volume… with the level typed in the bar
Queue spotify-queue live skips to the row (one Next per row ahead)
Spotify spotify-commands indexed, primary play or pause, next, previous, like, lyrics, sign out, and "Play " for each pinned one

Search lists Tracks, Artists, Albums, Playlists, Podcasts and Episodes as sections, five each, with the cover as the row's icon. A track row: enter plays it alone, cmd+enter adds it to the queue, cmd+l likes or unlikes it, cmd+p adds it to a playlist, cmd+o opens it in Spotify, cmd+c copies the link. A playlist or album: enter plays it, cmd+enter lists its tracks (a level whose rows play from that point inside the playlist, so the rest follows), cmd+s plays a playlist shuffled. The search waits 300 ms for the typing to settle and asks Spotify once per query; the rows show as soon as the results arrive, while which tracks you like (the Like or Unlike action) and your playlists are still being asked.

Add to playlist is on every track row (search, a playlist's or album's tracks, the library, the queue and its Now playing row) as a typed argument: with the cursor on the row the bar shows a Playlist select of the playlists a track can go into (your own and the collaborative ones; a followed one is not offered), Tab into it, pick, cmd+p posts the track there. enter still plays the row. The select is filled from one read of your playlists, kept for five minutes and shared with the Playlists palette and the pinned rows; before that read lands the select says "Loading playlists" and with none to offer "No playlists", and cmd+p then says so instead of posting. A pick without the value (a hotkey, pal run) asks in a form with the same select.

Library has four filters on Tab: Liked Songs (newest first, with the date), Recently played (the time, each track once), Top tracks and Top artists of the last weeks (ranked). Devices shows every device Spotify is open on with its kind's glyph, its volume and an active tag; the three Volume rows step the active device by 10 or mute it, and Set volume… takes a level (0 to 100) as its typed argument: with the cursor on it the bar shows the field, Tab into it, Enter sets it (a pick without the value, from pal run or a hotkey, asks in a form with the same field). Queue lists what plays now and what comes next, numbered; the Web API has no way to remove a row from the queue, so the row's Enter skips to it instead.

The Spotify rows are root results: type pause, next, like or Play Focus at the root. Each pinned setting entry (a playlist's name, or its spotify:playlist: link) is one "Play " row; a name that is not among your playlists is a hint row saying so. The playing track is also a row of the root's Now section (Enter opens the lyrics view).

Marked tracks (cmd-click, shift-click, shift+arrows) play as one list, queue in order, like or unlike together (as the row the pick is on reads), go to a playlist in one request (Add to playlist asks through a form, "Add 3 tracks") and copy their links one per line; marked playlists, albums and artists copy their links. Open is one row's.

The lyrics view

The cover large at the left with the track, artist and album under it, a progress bar that ticks with the times beside it, badges for paused, shuffle, repeat, liked and the device with its volume, and the lyrics beside it: the line playing bright and large, the three before it muted, the three after faint. As the song advances the lines slide up, the top one fades and a new one slides in at the bottom. Lyrics come from lrclib.net (free, no key): the exact lookup by track, artist, album and duration first, then a search by track and artist taking the closest duration within 3 s, once per track per run. Unsynced lyrics scroll in proportion to the position; a track lrclib does not have shows "No lyrics on lrclib" and f opens lrclib's search for it. The cover's dominant colour (a small sampler over the 300 px JPEG, decoded with jpeg-js) paints a band under the art and picks the progress bar's colour from the tag palette, so the tint follows the theme.

keys action
space, enter play or pause
left, right seek 10 s back or forward
up, down volume down or up by 5
l like or unlike
s shuffle on or off
r repeat: all, one, off
q the queue (a level over the view)
d devices
cmd+right, cmd+left next or previous track
cmd+c copy the line playing (the track as artist - title without synced lyrics)
cmd+o open in Spotify
f search lrclib, when it has no lyrics

Every key answers with the next tree at once from a state patched locally (a pause shows paused before Spotify confirms), and the API is read again on the next key. Signed out, the view says how to sign in (Enter opens the browser); with nothing playing, Enter opens Spotify.

It follows the song while it is open: the extension pushes the tree every second while something plays (view.update, the lines sliding up as the song advances, the bar ticking), from the moment the level comes on top until it leaves, and the app re-asks it every 5 s besides (refresh in the manifest) as the safety net. The bar popover is the same view in a compact layout, fed the same way.

The bar item

spotify/playing: the track playing (title · artist) beside the Spotify mark, or, with its Lyrics setting (lyrics, on), the lyric line playing when lrclib has synced lyrics for the track; hidden while nothing plays (unless its paused rule keeps the track muted while paused, or the core's show = "always" under [bar.items."spotify/playing"] keeps the glyph alone, muted; docs/config.md). A click opens the lyrics view in the popover, in a compact layout: the cover with the track, the artist and the album beside it, the progress bar with the times, the line playing large with one before and two after, the transport as key hints (space pause or play, cmd+left/cmd+right track, left/right seek 10 s, up/down volume), a row for l like, d devices, q queue with the paused/shuffle/repeat badges, and under a hairline the queue's next two tracks ("next", "then": the 64 px cover, the name, the artist), a click on one skipping to it (as many Nexts as its place, the queue palette's rule). The queue is asked at most every 15 s while the popover shows and again after a skip, an enqueue or a track change from the keys; the rows are left out while it is unknown. q and d open the queue and the devices inside the popover. While the popover is up the item is pushed every second, so the bar and the lines move; the playback state is read from Spotify every 5 s and the position between reads comes from the clock. The pushes go on for five minutes after the popover was last shown (view/hidden of its level ends the window early); outside that window the item asks to be rendered again exactly when the next lyric line starts, so the strip changes line on time without polling. The core also renders it every 30 s, on show, wake and network, and on its media trigger (macOS: the moment the track or the state changes, through the MediaRemote stream).

Setup

  1. Create an app at developer.spotify.com/dashboard: any name, the Web API checked, and the redirect URI http://127.0.0.1:27182/callback (the port is the Redirect port setting; change both together). Spotify requires the loopback address written as 127.0.0.1, not localhost.
  2. Put the app's client id under Settings › Extensions › Spotify. No secret: pal uses the authorization code flow with PKCE.
  3. Run any Spotify palette: the one row says "Sign in to Spotify"; Enter opens Spotify's consent page in the browser while pal listens on 127.0.0.1:27182. Approve, and the tab says pal has what it needs; the HUD says "Signed in to Spotify". The listener is up for five minutes at most and only while a sign-in is pending.

Playback control (play, pause, seek, volume, transfer) needs Spotify Premium; Spotify answers 403 Premium required otherwise and the panel says so. Search, the library and the lyrics work on any account.

Sign in again if Add to playlist answers "Could not add to the playlist: Insufficient client scope". The scopes pal asks for (playlist-read-private, playlist-read-collaborative, playlist-modify-public, playlist-modify-private, and user-library-modify for likes) have been in the consent page since the extension's first version, so a token from it has them; a token granted to a narrower list (a sign-in from an older build, or a consent trimmed at spotify.com/account/apps) does not, and Spotify answers the add with a 403 the toast relays. Sign Out of Spotify, then sign in once more.

Where the tokens live. The refresh token and the current access token are kept in the extension's storage file, <data dir>/pal/storage/spotify.json (~/Library/Application Support/pal/storage/spotify.json on macOS, ~/.local/share/pal/storage/ on Linux), readable by your user only. pal's secret settings can hold a keychain: reference, but the SDK has no settings.set an extension could write one through, so the file it is. The Sign Out of Spotify row deletes it; the app stays authorised on your account until you remove it at spotify.com/account/apps. The access token is renewed a minute before it expires and once on a 401; Spotify rotates the refresh token on each renewal and the new one is kept.

Settings, [extensions.spotify]:

key type default what
client_id text unset Your app's client id.
redirect_port number 27182 The loopback port for the sign-in redirect; must match the app's redirect URI.
pinned list [] Playlist names or spotify:playlist: links, each a "Play " root row.

playing item settings, [bar.items."spotify/playing".settings] (Settings › Bar, on the item's pane):

key type default what
lyrics boolean true The lyric line on the bar strip instead of the track name (when lrclib has synced lyrics).

Whether a paused track stays on the strip is the item's paused rule under Settings > Bar (Hidden off keeps it, muted). Keeping the glyph with nothing at all (its popover still offering play, sign in and the devices) is the core's show = "always" under [bar.items."spotify/playing"].

Limits and failures

  • A 429 is honoured: a Retry-After of two seconds or less is waited out inside the call, a longer one is remembered and every call until then answers "Spotify rate limit reached, try again in N s" without a request.
  • No network: one row, "Spotify is unreachable"; the bar item hides (it has no room for a hint) and comes back on the next render.
  • No active device: playing from a row is a toast naming the fix; the view says "No active device" with d for the devices.
  • Add to playlist refused: a 403 is "Could not add to the playlist" with Spotify's reason (the scope, above, or a playlist that is not yours to edit); a followed playlist never reaches the request, the select does not offer it.
  • lrclib down: the view says "Looking for lyrics" and asks again on the next key; a miss is remembered for the run, a failure is not.