Only show guides for your app
The first /play was the easy part. This is the architecture of everything after it — the per-guild queue, loop and shuffle, playlists, audio filters, and the lifecycle that keeps your bot from misbehaving — explained library-agnostically so your library's docs slot right in.
The Lavalink music bot guide gets you to a bot that plays one track. This guide is the map of everything past that — the queue, loop modes, playlists, filters, and the lifecycle that stops your bot leaking state between servers or squatting in an empty voice channel. It's deliberately library-agnostic: every Lavalink client names its methods differently, so here we cover the concepts and the shape of the state, and you fill in the exact calls from your library's docs.
| At a glance | |
|---|---|
| You need | a working Lavalink setup — the Lavalink music bot guide up and playing one track |
| You'll learn | how to structure a real music bot, not one method's syntax |
| Plan | free or premium — remember a music bot is two servers, each with its own timer on free |
| Time | about thirty minutes of reading |
Your bot holds the state — what's playing, what's next, whether it's looping. Lavalink holds the audio — it fetches and streams. Almost every feature below is a change to state your bot manages, then a single instruction to Lavalink through your client library. Keep that split clear and the rest falls into place.
And one rule underlies all of it: state is per-guild. Two servers can play different songs at once, so anything you store — the queue, the loop mode, the volume — lives keyed by guild ID, never in a single global variable. The classic first bug is a global queue array; the moment two servers use the bot, their songs collide.
A queue is just an ordered list of tracks per guild, plus a pointer to what's playing now. In plain JavaScript, before any library is involved:
// A per-guild store. The values are YOUR state; the audio lives in Lavalink.
const players = new Map(); // guildId -> { queue: [], current: null, loop: 'off' }
function stateFor(guildId) {
if (!players.has(guildId)) {
players.set(guildId, { queue: [], current: null, loop: 'off' });
}
return players.get(guildId);
}
The lifecycle:
/play — your library resolves the search or URL into one or more track objects. Push them onto queue. If nothing is playing, start the next track.queue and tell Lavalink to play it.🎯 Good to know: Skipping is just "end the current track early." Most libraries expose a stop/skip call that triggers the same track-ended event your advance logic already listens to — so skip, natural end, and
/stopall flow through one code path. Build that path once.
Loop is a small state machine with three modes. Read it when a track ends to decide what plays next:
| Loop mode | On track end |
|---|---|
| off | advance to the next track in the queue |
| track | replay the same track |
| queue | play the next track, and push the finished one back onto the end |
Shuffle is a one-time reorder of the pending queue (a Fisher–Yates shuffle of the array), not a mode — it rearranges what's already queued and leaves the current track alone. Store the loop mode in your per-guild state so /loop just cycles off → track → queue → off.
A single /play can add many tracks. When a user gives a playlist URL, your library's track resolver returns a list of tracks instead of one — enqueue them all and report how many you added. The distinction that trips people up:
Your library tells you which case you got (playlist vs search vs single track) in the resolve result. Handle the playlist case explicitly, or a 200-song playlist becomes one song.
Filters — bass boost, nightcore, karaoke, and friends — are a Lavalink feature, applied server-side to the audio while it streams. Your bot doesn't process audio; it sends Lavalink a filter configuration and Lavalink does the work. The families Lavalink offers:
| Filter | What it does |
|---|---|
| Equalizer | boost or cut frequency bands — the basis of "bass boost" |
| Timescale | change speed and pitch — the basis of "nightcore" and "slowed + reverb" |
| Karaoke | attenuate the vocal band |
| Tremolo / Vibrato | oscillate volume / pitch |
| Rotation | the "8D audio" pan effect |
| Distortion, ChannelMix, LowPass | tone-shaping and channel effects |
Your client library exposes these as a filters object on the player. The exact method names and value ranges are your library's — check its filter docs. Two honest cautions: filters stack (setting one usually replaces the whole filter config, so apply them together), and heavy filtering is more work for the Lavalink node, which matters on a RAM-limited free server.
⚠️ Heads up: Don't invent filter syntax from this table. It lists what exists in Lavalink; your library decides how you call it. Every method in this guide is a concept — the source of truth is your library's current documentation, exactly as in the Lavalink guide.
A music bot that only knows how to start is a bad citizen. Handle the whole life of a session:
/play starts clean rather than trying to resume a dead connection.By default, no — the Map above lives in memory and resets when the server stops (a free-plan timer expiry, a redeploy, a crash). For most music bots that's completely fine; people expect the queue to clear when the bot restarts. If you want it to survive, persist the pending queue to storage on change and reload it on start — see Storing data for your bot. Don't reach for that until you actually need it.