Queues
The model
Section titled “The model”A Queue is the playback state for a single guild. It maintains two arrays:
tracks— index 0 is the current track, everything after is upcomingpreviousTracks— tracks that have already played, most recent last
previousTracks[0..n] ← previousTrack | track (current) · tracks[1] · tracks[2] →next() advances the cursor — the current track moves to previousTracks and the next one becomes current. previous() moves it back. jump(n) teleports to any index in either direction, including negative indices into previousTracks.
Each Queue holds references to its VoiceState and Node:
queue.voice; // VoiceState — the voice connection for this guildqueue.node; // Node — shorthand for queue.voice.nodequeue.rest; // REST — shorthand for queue.voice.node.restqueue.guildId; // string — shorthand for queue.voice.guildIdCreating a queue
Section titled “Creating a queue”player.play() is the fastest path — it searches, creates the queue if needed, and starts playback:
await player.play("lofi hip hop", { guildId, voiceId: voiceChannelId });For more control, create the queue explicitly. If a voice connection doesn’t exist yet, queues.create() establishes it first:
const queue = await player.queues.create({ guildId, voiceId: voiceChannelId, node: "us-east", // optional: pin to a node context: { textChannelId }, // optional: attach your own data volume: 80, // optional: initial volume (0–1000) filters: { timescale: { speed: 1.2 } }, // optional: initial filters});If a queue already exists for the guild, create() returns it immediately without creating a duplicate.
Searching
Section titled “Searching”// Search via the player (uses the best available node)const result = await player.search("never gonna give you up");
// Search via the queue (uses the queue's assigned node)const result = await queue.search("query", "scsearch");The second argument is the search prefix. It defaults to player.options.queryPrefix ("ytsearch" by default). Common values:
| Prefix | Source |
|---|---|
ytsearch | YouTube (default) |
ytmsearch | YouTube Music |
scsearch | SoundCloud |
Direct URLs are detected automatically and bypass the prefix entirely.
switch (result.type) { case "track": queue.add(result.data); break; case "playlist": queue.add(result.data); break; case "query": queue.add(result.data[0]!); break; case "empty": /* no results */ break; case "error": console.error(result.data.message); break;}Adding tracks
Section titled “Adding tracks”queue.add() accepts a Track, an array of Tracks, or a Playlist. The optional second argument is shallow-merged into every track’s userData:
queue.add(track);queue.add([track1, track2, track3]);queue.add(playlist);queue.add(tracks, { requestedBy: userId, requestedAt: Date.now() });add() returns the queue itself, so it’s chainable.
Queue state
Section titled “Queue state”queue.track; // current Track | nullqueue.previousTrack; // most recent previous Track | nullqueue.tracks; // Track[] — index 0 is current, rest are upcomingqueue.previousTracks; // Track[] — most recent last
queue.length; // number of current + upcoming tracksqueue.totalLength; // all tracks including previous
queue.playing; // true when unpaused AND Lavalink has a track loadedqueue.paused; // true when the Lavalink player is pausedqueue.stopped; // true when queue has a current track but Lavalink doesn't// (track was stopped via stop() — resume() will restart it)queue.finished; // true when tracks[] is empty (no current or next)queue.empty; // true when both tracks[] and previousTracks[] are emptyqueue.hasNext; // true when tracks.length > 1queue.hasPrevious; // true when previousTracks.length > 0queue.destroyed; // true when this instance is no longer the active queue for the guild
queue.volume; // current volume (0–1000)queue.repeatMode; // 'none' | 'track' | 'queue'queue.autoplay; // boolean
queue.duration; // total ms of current + upcoming tracks (live tracks excluded)queue.formattedDuration; // e.g. "1:23:45"queue.currentTime; // estimated playback position in ms, interpolated between ticksqueue.formattedCurrentTime; // e.g. "2:34"The distinction between stopped, paused, and finished matters:
paused— Lavalink has the track loaded but is not advancing.resume()unpauses.stopped— the queue has a track at index 0 but Lavalink has no track.resume()callsjump(0)to reload it.finished—tracks[]is empty. There is no current track to resume.
Track properties
Section titled “Track properties”track.id; // source identifier (e.g. YouTube video ID)track.title; // track title (default: "Unknown Track")track.author; // track author (default: "Unknown Author")track.duration; // duration in ms (Infinity for live streams)track.formattedDuration; // e.g. "3:45" or "Live"track.isLive; // true for streamstrack.isSeekable; // true if seeking is supportedtrack.uri; // source URI (may be null)track.url; // same as uri if it's a valid URL, otherwise nulltrack.artworkUrl; // artwork image URL (may be null)track.isrc; // ISRC code (may be null)track.sourceName; // e.g. 'youtube', 'soundcloud', 'bandcamp'track.encoded; // base64 encoded track data (used by Lavalink)track.userData; // your attached data — typed via augmentationtrack.pluginInfo; // plugin-provided metadata — typed via augmentationtrack.toString() returns track.title, so tracks can be used directly in template literals.
Playlist properties
Section titled “Playlist properties”When a search returns type: 'playlist', the data is a Playlist:
playlist.name; // playlist name (default: "Unknown Playlist")playlist.tracks; // Track[]playlist.selectedTrack; // index of the track the source URL pointed to, -1 if noneplaylist.duration; // total ms (live tracks excluded)playlist.formattedDuration; // e.g. "4:32:10"playlist.pluginInfo; // plugin-provided metadata — typed via augmentationplaylist.toString() returns playlist.name.
Syncing with Lavalink
Section titled “Syncing with Lavalink”If local state drifts — most commonly after a node reconnect — you can sync in either direction:
await queue.sync(); // pull Lavalink's state into the local queue (default)await queue.sync("remote"); // push local queue state to Lavalink
// Sync all queues on a node at onceawait player.queues.sync("us-east", "local");await player.queues.sync("us-east", "remote");autoSync: true (the default) calls queues.sync(node, 'remote') automatically when a node reconnects without resuming its session, so local state is pushed to the fresh Lavalink session.
Autoplay
Section titled “Autoplay”When the queue empties and autoplay is on, discolink calls fetchRelatedTracks. You provide the implementation:
const player = new Player({ async fetchRelatedTracks(queue, track) { // track is the one that just finished // return Track[] to continue, [] to stop return []; },});
queue.setAutoplay(true);Add related tracks manually without enabling autoplay:
await queue.addRelated(); // uses current or previous track as referenceawait queue.addRelated(specificTrack); // use a specific reference trackDestroying a queue
Section titled “Destroying a queue”Always destroy queues when they’re no longer needed:
await queue.destroy("Channel deleted");await player.queues.destroy(guildId, "reason"); // via managerdestroy() calls Lavalink’s REST API to remove the player, emits queueDestroy, then delegates to voices.destroy() to tear down the voice connection.
Full API: Queue