🔊 sonoscli
May 14, 2026 · View on GitHub
sonoscli is a modern Go CLI to control Sonos speakers over your local network (UPnP/SOAP).
Features
- Reliable discovery: SSDP + topology (
ZoneGroupTopology.GetZoneGroupState) with subnet scan fallback. - Coordinator-aware control: target any room; commands go to the group coordinator automatically.
- Playback controls: play/pause/stop/next/prev, plus
play-uri,linein, andtv. - Grouping: inspect groups, join/unjoin, party mode, dissolve groups, and solo a room.
- Queue: list/play/remove/clear queue entries.
- Favorites: list and play Sonos Favorites by index or title.
- Scenes: save/apply presets (grouping + per-room volume/mute).
- Spotify:
- Enqueue/play Spotify share links or canonical
spotify:<type>:<id>URIs (no Spotify credentials required). - Search Spotify via SMAPI (Sonos Music API; uses your linked service in Sonos).
- Optional Spotify Web API search (client credentials) if you want it.
- Enqueue/play Spotify share links or canonical
- YouTube handoff: resolve a YouTube URL with
yt-dlpand hand the direct audio stream to Sonos. - Smart URL streaming:
play-urlruns a short-lived local MP3 proxy for YouTube, YouTube Music playlists, podcasts, radio streams, SoundCloud-style pages, and other URLs. - Live events:
watchsubscribes to AVTransport + RenderingControl and prints changes. - Scriptable output:
--format plain|json|tsvplus--debugtracing.
This is not an official Sonos project.
Requirements
- Your machine must be on the same network as your Sonos system.
- Speakers must be reachable on TCP port
1400(e.g.http://<speaker-ip>:1400/).
Spotify:
- Spotify must already be linked in the Sonos app.
- This CLI does not authenticate with Spotify; it enqueues Sonos “Spotify” URIs/metadata.
Spotify search (recommended, no Spotify Web API credentials):
sonos smapi searchuses Sonos SMAPI to search linked services (e.g. Spotify).- Some services require a one-time DeviceLink/AppLink flow:
sonos auth smapi begin|complete.
Spotify search via Spotify Web API (optional):
- If you want
sonos search spotify, you’ll need a Spotify Web API app (client credentials). SetSPOTIFY_CLIENT_IDandSPOTIFY_CLIENT_SECRET(or pass--client-id/--client-secret).
YouTube:
- Install
yt-dlpif you wantsonos play youtube. - Sonos plays the resolved temporary audio stream directly; long videos may expire and need resolving again.
- For most YouTube playback, prefer
sonos play-url: it proxies the stream through your machine, transcodes to MP3, and avoids Sonos having to fetch YouTube's temporary media URL itself.
Smart URL streaming:
- Install
yt-dlpandffmpegforsonos play-url. play-urlresolves common media pages, pipesyt-dlpsources intoffmpeg, transcodes to a local MP3 stream, sends the resolved title/provider to Sonos metadata, and exits the proxy when playback ends or goes idle.- Unambiguous YouTube / YouTube Music playlist URLs (
?list=…without?v=…) are auto-detected and enqueued track-by-track. Use--playlist,--no-playlist, and--playlist-limitto control playlist handling.
Install / build
Install (Homebrew, single line):
brew install steipete/tap/sonoscli
This installs the sonos binary.
Upgrade later:
brew upgrade steipete/tap/sonoscli
Install from source (Go):
go install github.com/steipete/sonoscli/cmd/sonos@latest
sonos --version
Build a local binary:
go build -o sonos ./cmd/sonos
./sonos --version
./sonos --help
Docker:
docker build -t sonoscli .
docker run --rm --network host -v "$PWD/.sonoscli:/data" sonoscli discover
Linux containers need --network host for SSDP/UPnP discovery. The image includes ffmpeg, yt-dlp, and curl.
Quick start
Note: if you installed via Homebrew or go install, replace ./sonos with sonos.
Discover speakers:
./sonos discover
./sonos discover --format json
./sonos discover --all # include invisible/bonded devices (advanced)
Show status (text or JSON):
./sonos status --name "Kitchen"
./sonos now --name "Kitchen"
./sonos status --name "Kitchen" --format json
Playback:
./sonos play --name "Kitchen"
./sonos pause --name "Kitchen"
./sonos stop --name "Kitchen"
./sonos next --name "Kitchen"
./sonos prev --name "Kitchen"
Watch live events (track/volume changes):
./sonos watch --name "Kitchen"
./sonos watch --name "Kitchen" --format json
./sonos watch --name "Kitchen" --format tsv
Note: this starts a local callback server for UPnP events; your OS firewall may prompt to allow incoming connections.
Command overview
Run sonos --help for the full list. Most commonly used:
- Discovery & status:
discover,status/now,watch - Playback:
play,pause,stop,next,prev,open,enqueue,play-url,play-uri,linein,tv - Grouping:
group status,group join,group unjoin,group solo,group party,group dissolve - Queue:
queue list,queue play,queue remove,queue clear - Favorites:
favorites list,favorites open - Scenes:
scene save,scene apply,scene list,scene delete - Spotify search:
smapi search(recommended), optionalsearch spotify(Spotify Web API)
Queue
List the queue:
./sonos queue list --name "Kitchen"
./sonos queue list --name "Kitchen" --format json
Play or remove a queue entry (positions are 1-based):
./sonos queue play --name "Kitchen" 1
./sonos queue remove --name "Kitchen" 3
Clear the queue:
./sonos queue clear --name "Kitchen"
Scenes (presets)
Save a scene (grouping + per-room volume/mute):
./sonos scene save "Evening"
Apply a scene later:
./sonos scene apply "Evening"
List / delete scenes:
./sonos scene list
./sonos scene delete "Evening"
Scenes are stored in your user config dir as sonoscli/scenes.json (e.g. ~/.config/sonoscli/scenes.json on macOS/Linux).
Favorites
List Sonos Favorites:
./sonos favorites list --name "Kitchen"
./sonos favorites list --name "Kitchen" --format json
Play by index (from the list):
./sonos favorites open --name "Kitchen" --index 1
Or play by title (case-insensitive exact match):
./sonos favorites open --name "Kitchen" "BBC Radio 6 Music"
Other sources
Play an arbitrary URI:
./sonos play-uri --name "Kitchen" "https://example.com/stream.mp3"
Play a URL through the Sonos-safe local proxy (requires ffmpeg; yt-dlp for YouTube and media pages):
./sonos play-url --name "Kitchen" "https://www.youtube.com/watch?v=-n_rdQIVahw"
./sonos play-url --name "Kitchen" "https://music.youtube.com/playlist?list=PL..."
./sonos play-url --name "Kitchen" --playlist-limit 10 "https://music.youtube.com/playlist?list=PL..."
./sonos play-url --name "Kitchen" "https://example.com/podcast/episode.mp3"
Force radio-style playback (useful for station-like streams):
./sonos play-uri --name "Kitchen" --radio --title "My Stream" "https://example.com/live.mp3"
Switch to line-in (optionally from another speaker):
./sonos linein --name "Kitchen" --from "Living Room"
Switch to TV input (soundbar):
./sonos tv --name "Living Room"
Grouping
Show current groups:
./sonos group status
./sonos group status --all # include invisible/bonded devices (advanced)
Join Bedroom into Living Room’s group:
./sonos group join --name "Bedroom" --to "Living Room"
Room targeting supports fuzzy substring matching (and will suggest matches on ambiguity):
./sonos group join --name "Off" --to "Bar" # "Office" joins "Bar"
./sonos group join --name "Bed" --to "Liv" # "Bedroom" joins "Living Room"
Ungroup a speaker (make it standalone):
./sonos group unjoin --name "Bedroom"
Solo a speaker (ungroup its current group so it plays alone):
./sonos group solo --name "Office"
Party mode (join all visible speakers to a target group):
./sonos group party --to "Bar"
Dissolve a group (ungroup all members of the group):
./sonos group dissolve --name "Living Room"
Ungroup Office and play on Office only:
./sonos group solo --name "Office"
./sonos open --name "Office" "https://open.spotify.com/album/<id>"
Re-join a speaker back into another group:
./sonos group join --name "Office" --to "Bar"
Group volume / mute (affects the whole group):
./sonos group volume get --name "Living Room"
./sonos group volume set --name "Living Room" 25
./sonos group mute get --name "Living Room"
./sonos group mute toggle --name "Living Room"
Volume / mute:
./sonos volume get --name "Kitchen"
./sonos volume set --name "Kitchen" 25
./sonos mute get --name "Kitchen"
./sonos mute toggle --name "Kitchen"
Targeting and groups
Target a speaker by:
--name "Kitchen"(Sonos room name)--ip 192.168.0.250(speaker IP)
Most commands must be sent to the group coordinator (the device that owns transport state for the group). sonoscli resolves the coordinator automatically so commands behave like the Sonos app.
Spotify
Search via Sonos (SMAPI; no Spotify Web API credentials):
./sonos smapi services
./sonos smapi categories --service "Spotify"
./sonos smapi browse --service "Spotify" --id root
./sonos auth smapi begin --service "Spotify"
# open the printed URL in a browser, link your account, then:
./sonos auth smapi complete --service "Spotify" --code <linkCode> --wait 5m
./sonos smapi search --service "Spotify" --category tracks "gareth emery"
./sonos smapi search --service "Spotify" --category tracks --open --name "Office" "gareth emery"
Some AppLink services only return a native app authentication URL and no device-link code. In that case, auth smapi begin prints the app URL and notes that sonoscli cannot complete token storage automatically.
Play from a search query (shortcut for SMAPI search + open):
./sonos play spotify --name "Office" "gareth emery"
./sonos play spotify --name "Office" --category albums "gareth emery"
SMAPI tokens are stored under your user config dir as sonoscli/smapi_tokens.json (e.g. ~/.config/sonoscli/smapi_tokens.json on macOS/Linux).
Search via Spotify Web API (prints playable URIs):
export SPOTIFY_CLIENT_ID="..."
export SPOTIFY_CLIENT_SECRET="..."
./sonos search spotify --type track --limit 5 "daft punk harder better"
./sonos search spotify --type playlist "focus"
Open the first search result directly on Sonos:
./sonos search spotify --open --name "Kitchen" "miles davis so what"
Enqueue + play:
./sonos open --name "Kitchen" spotify:track:6NmXV4o6bmp704aPGyTVVG
./sonos open --name "Kitchen" https://open.spotify.com/track/6NmXV4o6bmp704aPGyTVVG
Enqueue only:
./sonos enqueue --name "Kitchen" spotify:playlist:37i9dQZF1DXcBWIGoYBM5M
Notes:
- The enqueue implementation tries Spotify Sonos service numbers
2311and3079for compatibility. - Use
--titleto override the queue display title for some entries.
Dev workflow
Makefile
make fmt
make test
make build
make lint
make lint requires golangci-lint:
brew install golangci-lint
# or:
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
pnpm helper scripts
This repo includes a minimal package.json so you can drive the workflow with pnpm (no Node dependencies required):
pnpm build
pnpm test
pnpm format
pnpm lint
pnpm sonos -- discover
pnpm sonos -- status --name "Kitchen"
pnpm sonos -- open --name "Kitchen" spotify:track:6NmXV4o6bmp704aPGyTVVG
pnpm sonos -- search spotify "miles davis so what"
pnpm sonos -- group status
pnpm sonos -- queue list --name "Kitchen"
CI runs: gofmt check, go vet, go test, and golangci-lint.
Global flags
--ip <ip>: target by IP--name <name>: target by speaker name (defaults tosonos config defaultRoomif set)--timeout <duration>: discovery/network timeout (default15s, configurable withsonos config set defaultTimeout 10s)--format plain|json|tsv: output format (defaults tosonos config formatif set)--json: deprecated alias for--format json--debug: enable detailed trace logs (SSDP/topology/SOAP timings)
Config (defaults)
Persist small local defaults so repeated commands stay terse:
./sonos config get
./sonos config set defaultRoom "Office"
./sonos config set defaultTimeout 10s
./sonos config set format json
./sonos config unset defaultRoom
Supported keys:
defaultRoom: room used when--name/--ipis omitted.defaultTimeout: discovery/network timeout used when--timeoutis omitted. The built-in default is15s.format: default output format (plain,json, ortsv).
Snake-case aliases (default_room, default_timeout) are accepted too.
Troubleshooting
discoveris empty:- Some networks block multicast/SSDP;
sonosclifalls back to scanning local /24 subnets for port1400and then uses Sonos topology to list all rooms. - Ensure Wi‑Fi client isolation is off and you’re on the same LAN/subnet.
- Some networks block multicast/SSDP;
- Discovery is slow or flaky:
- The default timeout is
15s. Usesonos config set defaultTimeout 20sto make a longer timeout sticky, or pass--timeout 5sin scripts that should fail fast. - Run
sonos --debug discoverto see whether SSDP multicast is timing out and whether topology calls are slow.
- The default timeout is
- Discovery / SOAP calls hang or time out on your network:
sonoscliretries local Sonos HTTP/SOAP calls viacurlas a workaround for some network/firmware quirks.
- Commands fail with UPnP/SOAP errors:
- Verify you can reach
http://<speaker-ip>:1400/from this machine. - Try targeting by
--name(it resolves the coordinator).
- Verify you can reach
- Spotify enqueue fails:
- Confirm Spotify is linked and playable in the Sonos app.
- Some systems behave differently per firmware/service configuration.
Inspiration / references
This project was informed by the Sonos control ecosystem and the SoCo Python library:
https://github.com/SoCo/SoCo
Design doc
See docs/spec.md.
License
MIT License. See LICENSE.