Search Spotify and control playback from a Telescope picker without leaving Neovim.
Note
jam.nvim is an early preview. Spotify is the first provider, and support for additional music services is planned as their official APIs allow.
- Live Spotify search for music, playlists, podcasts, and episodes
- Drill-down for album tracks, artist top tracks, podcast episodes, and playlist items
- Play, pause, skip, go back, and add tracks or episodes to the queue
- OAuth Authorization Code flow with PKCE—no client secret in your config
- Album-art previews through
image.nvimorchafa, with automatic detection - Contextual metadata for artists, albums, tracks, podcasts, and episodes
:Jamand:Telescope jamentry points- Health diagnostics with
:checkhealth jam
Required:
- Neovim 0.10+
- telescope.nvim
curlfor Spotify API requestsopensslfor PKCE authentication- A Spotify account and application client ID
- Spotify Premium and an active Spotify Connect device for playback control
Run :checkhealth jam after installation to verify these dependencies and your
configuration.
Album artwork requires an optional renderer. Without one, the preview shows the image URL as text.
- Install
chafafor a portable, full-color character-art preview (brew install chafaon macOS orsudo apt install chafaon Debian/Ubuntu). - Or install image.nvim and use a compatible terminal such as Kitty or WezTerm.
chafa is the simplest cross-terminal option. Run :checkhealth jam to see
which artwork backend jam.nvim detected.
{
"bautistaaa/jam.nvim",
dependencies = {
"nvim-telescope/telescope.nvim",
-- Optional, for high-resolution artwork:
-- { "3rd/image.nvim", opts = {} },
},
cmd = { "Jam" },
opts = {
providers = {
spotify = {
client_id = vim.env.SPOTIFY_CLIENT_ID,
},
},
},
}vim.pack.add({
"https://github.com/nvim-telescope/telescope.nvim",
"https://github.com/bautistaaa/jam.nvim",
})
require("jam").setup({
providers = {
spotify = { client_id = vim.env.SPOTIFY_CLIENT_ID },
},
})local add = MiniDeps.add
add({ source = "nvim-telescope/telescope.nvim" })
add({ source = "bautistaaa/jam.nvim" })use({
"bautistaaa/jam.nvim",
requires = { "nvim-telescope/telescope.nvim" },
config = function()
require("jam").setup({
providers = {
spotify = { client_id = vim.env.SPOTIFY_CLIENT_ID },
},
})
end,
})Plug 'nvim-telescope/telescope.nvim'
Plug 'bautistaaa/jam.nvim'Then call require("jam").setup(...) from your Lua config.
-
Sign in to the Spotify Developer Dashboard.
-
Select Create app.
-
Enter an app name and description. A website is not required for local use.
-
Add this exact redirect URI:
http://127.0.0.1:8765/callback -
Select Web API when Spotify asks which APIs or SDKs the app will use, accept the terms, and save the app.
-
Open the app's settings and copy its Client ID. jam.nvim does not need the client secret; never put the secret in your Neovim configuration.
-
Put the client ID in your shell environment:
export SPOTIFY_CLIENT_ID="your-client-id"
Add that line to
~/.zshrc,~/.bashrc, or the equivalent for your shell, then start Neovim from a new terminal. -
Run
:checkhealth jamand resolve any reported errors. -
Run
:Jam auth spotifyand finish authorization in the browser. -
Open Spotify and play something once so Spotify Connect marks the selected device as active.
-
Run
:Jam, enter a search, and press<CR>to play a result.
Tokens are stored with 0600 permissions under Neovim's data directory. Run
:Jam logout to remove them.
Device not found: jam.nvim opens the selected item in Spotify. Once the app is ready, select the item again. If needed, manually play a track once so the Web API considers the device active.- Artwork URL instead of an image: Install
chafa, or configureimage.nvimin a compatible terminal, then reopen the picker. - Client ID is not configured: Confirm
:echo $SPOTIFY_CLIENT_IDprints your client ID. Restart Neovim from a new terminal after changing your shell configuration. - OAuth redirect errors: Confirm the redirect URI in Spotify is exactly
http://127.0.0.1:8765/callback.
:Jam " Open search
:Jam search " Open search
:Jam auth spotify " Connect Spotify
:Jam play
:Jam pause
:Jam next
:Jam previous
:Jam now-playing
:Jam logout
:Jam health
:Telescope jamSearch filters:
| Prefix | Searches |
|---|---|
a: |
Albums |
t: |
Artists |
s: |
Songs/tracks |
l: |
Playlists |
p: |
Podcasts |
e: |
Podcast episodes |
There is no separate playlist tab. Unfiltered search mixes types in the results
list (labeled TRACK, ALBUM, ARTIST, PLAYLIST, PODCAST, EPISODE). Use
l: when you only want playlists.
For example, a:Abbey Road, t:BTS, s:One More Night, l:Discover Weekly,
p:Radiolab, or e:Black Holes.
Picker mappings:
| Mapping | Action |
|---|---|
<CR> |
Open a collection or play the selected track/episode |
<C-q> |
Add selection to queue |
<C-p> |
Pause playback |
<Esc> |
Return from a collection to the original search |
Selecting an album opens its tracks in disc and track order. Selecting an artist
opens their top tracks, selecting a podcast opens its episodes, and selecting a
playlist opens its items when Spotify allows it. Spotify Development Mode only
returns playlist contents for playlists you own or collaborate on; for other
playlists, jam.nvim plays the playlist instead. Press <Esc> in any collection
view to return to the same search query.
require("jam").setup({
provider = "spotify",
search = {
debounce_ms = 250,
limit = 10,
types = { "track", "album", "artist", "playlist", "show", "episode" },
},
artwork = {
enabled = true,
backend = "auto", -- auto, image, chafa, text, or none
width = 38,
height = 16,
cache = true,
},
picker = {
layout_strategy = "flex",
},
providers = {
spotify = {
client_id = vim.env.SPOTIFY_CLIENT_ID,
redirect_uri = "http://127.0.0.1:8765/callback",
},
},
})Provider adapters declare capabilities and implement the common search and playback interface. The Telescope UI does not call Spotify-specific endpoints, so additional providers can expose only the capabilities their APIs support.
Spotify is currently the only implemented provider. More providers are planned, but no specific service or timeline is promised: authentication, catalog access, and playback controls vary significantly between platforms, and some services do not offer an official public playback API. Future adapters may therefore support search, opening items in a native app, or playback controls in different combinations.