A professional-grade, TypeScript-first Lavalink v4 client library for Node.js
v3.1.0 - Major upgrade with auto node failover, player migration, Lavalink 4.20 improvements, and DAVE readiness reporting.
⚡ Build resilient Discord music bots with Lavalink v4.20+, automatic failover, typed APIs, and production-ready runtime diagnostics.
- 🚀 Production-first runtime — Node failover, reconnect logic, startup diagnostics, and safer voice forwarding.
- 🧠 Developer-friendly API — TypeScript-first, fluent filters, clear events, and easy plugin integration.
- 🔒 Modern compatibility — Lavalink v4.20-ready behavior with DAVE readiness reporting and strict voice-state support.
- ➜ Features
- ➜ Installation
- ➜ Quick Start
- ➜ Copy-Paste Quickstart
- ➜ Examples
- ➜ API Documentation
- ➜ Architecture
- ➜ Events
- ➜ Support
- Modern Update Player Payload - Uses
track.encoded/track.identifierformat with support foruserData - Track End Reason Compatibility - Handles both legacy and newer reason formats (e.g.
finishedandFINISHED) - Improved Reconnection - Reconnects with proper Discord bot client ID for stable node recovery
- Session & Version Helpers - New manager/node helpers to inspect Lavalink versions and active session players
- DAVE is negotiated in Discord voice infrastructure and Lavalink server internals
- This client library remains compatible at the control/API layer, but full DAVE media encryption behavior depends on your Lavalink server version/configuration
- Recommended: run latest Lavalink 4.20.x build and validate voice connectivity in your target guild setup
- ✅ Advanced Queue Management -
playNext(),moveTrack(),swapTracks(),jumpTo(),getQueueInfo(),getUpcoming() - ✅ Player Statistics - Session metrics and history tracking
- ✅ Automatic Player Failover - Auto-migrate players to healthy nodes when one disconnects
- ✅ DAVE Readiness API - Inspect compatibility with
getDaveReadinessReport() - ✅ Structured v3 Example - Production-ready JS example for failover + diagnostics
- ✅ Comprehensive Guides - Setup, visual guide, examples comparison, and index
- 🎯 Full Lavalink v4 Support - Complete implementation of the latest REST API and WebSocket protocol
- 🏗️ TypeScript-First - Strongly typed APIs with full autocomplete support
- ⚡ Smart Load Balancing - Advanced penalty system for optimal node selection
- 🌍 Voice Region Optimization - Auto-select nodes based on Discord voice region
- 🔄 Automatic Reconnection - Exponential backoff with jitter for fault tolerance
- ⏱️ Rate Limiting Handler - Built-in retry strategies (exponential/linear/none)
- 🎨 Fluent Filter API - Chainable filter configuration for elegant code
- 🎭 Event-Driven - Comprehensive event system for reactive programming
- 🔌 Plugin Support - Extend functionality with custom plugins
- 🛡️ Production-Ready - Battle-tested design patterns and error handling
- 🚚 State-Aware Node Migration - Preserves voice state, volume, filters, playback position, and pause state across node migration
▶️ Full Playback Control - Play, pause, resume, stop, seek- ⏭️ Queue Management - Add, remove, clear, shuffle queue
- ⏮️ Previous Track - Play previous tracks (last 10 stored)
- 🔀 Shuffle - Randomize queue order
- 🔁 Loop Modes - Track loop, queue loop, or off
- 📜 Playback History - Track last 50 played songs
- ⭐ Favorites/Bookmarks - Save favorite tracks per user/guild
- 💾 Queue Persistence - Save and restore queue state
- 🔊 Volume Control - 0-100% volume adjustment with normalization
- 🎛️ Speed/Pitch Control - Independent speed and pitch adjustment (0.25x-3.0x)
- 🌊 Crossfade - Smooth transitions between tracks
- 🎵 Gapless Playback - Seamless track transitions
- 🔍 Multi-Platform Search - YouTube, YouTube Music, SoundCloud support
- 📊 Position Tracking - Real-time playback position updates
- 🤖 Intelligent AutoPlay - Automatically plays related tracks when queue ends (YouTube, SoundCloud, Spotify)
- 💿 Metadata Cache - Cache track info to reduce API calls
- 🎚️ 10 Filter Types - Volume, Equalizer, Karaoke, Timescale, Tremolo, Vibrato, Rotation, Distortion, ChannelMix, LowPass
- 🎵 8 Equalizer Presets - Bass, Treble, Soft, Flat, Electronic, Rock, Classical, Pop
- ✨ 5 Effect Presets - Nightcore, Vaporwave, 8D Audio, Bassboost (4 levels), Soft
- 🔗 Chainable API - Fluent filter configuration with
filters().effect1().effect2().apply()
npm install lavaflowimport { Client, GatewayIntentBits } from 'discord.js';
import { Manager } from 'lavaflow';
const client = new Client({
intents: [
GatewayIntentBits.Guilds,
GatewayIntentBits.GuildVoiceStates,
],
});
const manager = new Manager({
nodes: [
{
name: 'main',
host: 'localhost',
port: 2333,
password: 'youshallnotpass',
secure: false,
},
],
send: (guildId, payload) => {
const guild = client.guilds.cache.get(guildId);
if (guild) guild.shard.send(payload);
},
autoPlay: true,
debug: true,
});
client.once('clientReady', async () => {
await manager.init(client.user!.id);
console.log(`Ready as ${client.user!.tag}`);
});
client.on('raw', (packet) => {
if (manager.isInitialized()) {
manager.updateVoiceState(packet);
}
});
client.login(process.env.DISCORD_TOKEN);import { Manager } from 'lavaflow';
import { Client } from 'discord.js';
const client = new Client({ intents: ['Guilds', 'GuildVoiceStates'] });
const manager = new Manager({
nodes: [
{
name: 'Node 1',
host: 'localhost',
port: 2333,
password: 'youshallnotpass',
secure: false
}
],
send: (guildId, payload) => {
const guild = client.guilds.cache.get(guildId);
if (guild) guild.shard.send(payload);
},
autoPlay: true,
debug: false
});
// Initialize manager after Discord client is ready
client.once('clientReady', () => {
manager.init(client.user!.id);
});
// Forward voice state updates
client.on('raw', (packet) => {
if (manager.isInitialized()) {
manager.updateVoiceState(packet);
}
});
// Create and use a player
const player = manager.create({
guildId: 'guild-id',
voiceChannelId: 'voice-channel-id',
textChannelId: 'text-channel-id'
});
await player.connect();
const result = await player.search('Never Gonna Give You Up');
if (result.loadType === 'track') {
await player.play(result.data);
}// Add to front of queue (play next)
player.playNext(track);
// Reorder queue
player.moveTrack(fromIndex, toIndex);
// Swap two tracks
player.swapTracks(indexA, indexB);
// Jump to track and play immediately
await player.jumpTo(trackIndex);
// Get queue summary
const info = player.getQueueInfo();
console.log(`${info.length} tracks, ${info.totalDurationMs}ms total`);
// Peek at next 5 tracks
const upcoming = player.getUpcoming(5);
// Get player metrics
const metrics = player.getMetrics();
console.log(`Played ${metrics.tracksPlayed} tracks this session`);
// Get history statistics
const stats = player.getHistoryStats();
console.log(`${stats.skipped} tracks skipped`);
// Lavalink version visibility (useful for upgrade audits)
const versions = await manager.getNodeVersions();
for (const [node, version] of versions) {
console.log(`${node}: ${version}`);
}
// DAVE readiness diagnostics
const dave = await manager.getDaveReadinessReport();
console.log(`DAVE ready: ${dave.summary.ready}`);
// Startup diagnostics
const diagnostics = await manager.runStartupDiagnostics();
console.log(`Healthy nodes: ${diagnostics.healthyNodes}/${diagnostics.totalNodes}`);node examples/v3-structured-bot.jsThis example demonstrates:
- auto failover with
autoMovePlayersOnNodeDisconnect - node version visibility (
!nodes) - DAVE readiness reporting (
!dave)
Start with the production-ready bot example (19 commands, rich embeds, interactive UI):
node examples/advanced-bot.jsCommands: !play, !search, !skip, !queue, !playnext, !move, !swap, !jump, !stats, !queueinfo, !upcoming, and more!
See examples/ADVANCED_BOT_GUIDE.md for complete documentation.
// Use filters with fluent API
await player.filters()
.timescale({ speed: 1.2, pitch: 1.0, rate: 1.0 })
.tremolo({ frequency: 4.0, depth: 0.5 })
.karaoke()
.apply();
// Advanced player features
player.setLoopMode('track'); // Loop current track
player.setLoopMode('queue'); // Loop entire queue
player.setLoopMode('off'); // Disable loop
await player.setSpeed(1.5); // 1.5x speed (independent)
await player.setPitch(1.2); // Higher pitch (independent)
await player.setSpeedAndPitch(1.3, 1.3); // Both together
player.setCrossfade(3000); // 3 second crossfade
player.setVolumeNormalization(true); // Enable normalization
// Favorites
import { FavoritesManager } from 'lavaflow';
const favorites = new FavoritesManager();
favorites.addUserFavorite('userId', track);
favorites.addGuildFavorite('guildId', track, 'userId');
const userFavs = favorites.getUserFavorites('userId');
// Queue persistence
const saved = player.saveQueue(); // Returns JSON string
// Later...
await player.restoreQueue(saved); // Restore queue state
// Playback history
const history = player.getHistory(); // Last 50 tracks
player.clearHistory();
// Metadata cache
import { MetadataCache } from 'lavaflow';
const cache = new MetadataCache(1000, 3600000); // 1000 items, 1 hour TTL
const cachedTrack = cache.get('identifier');
if (!cachedTrack) {
const track = await player.search('query');
cache.set('identifier', track);
}🏗️ Expand Architecture
Manages multiple Lavalink nodes with intelligent load balancing based on:
- Player count (active and playing players)
- CPU load (normalized by core count)
- Memory usage
- Custom penalty calculations
Feature-rich player with:
- Play, pause, stop, seek controls
- Volume management
- Filter support (v4 filters: Timescale, Tremolo, Karaoke, Distortion, Rotation, etc.)
- Queue management
- Voice state handling
Chainable API for audio filters:
player.filters()
.volume(0.8)
.equalizer([{ band: 0, gain: 0.2 }])
.timescale({ speed: 1.1 })
.tremolo({ frequency: 2.0, depth: 0.5 })
.vibrato({ frequency: 2.0, depth: 0.5 })
.rotation({ rotationHz: 0.2 })
.distortion({ sinOffset: 0, sinScale: 1, cosOffset: 0, cosScale: 1, tanOffset: 0, tanScale: 1, offset: 0, scale: 1 })
.channelMix({ leftToLeft: 1, leftToRight: 0, rightToLeft: 0, rightToRight: 1 })
.lowPass({ smoothing: 20 })
.apply();nodeConnect- Node connected successfullynodeDisconnect- Node disconnectednodeError- Node encountered an errornodeReconnect- Node attempting reconnectiontrackStart- Track started playingtrackEnd- Track finished playingtrackStuck- Track got stucktrackException- Track encountered an errorplayerCreate- Player createdplayerDestroy- Player destroyedplayerNodeMigrate- Player moved from one node to anotherqueueChanged- Queue mutation event emittedqueueEnd- Queue finished playingdebug- Debug messages (only ifdebug: true)
📚 Expand Examples
See simple-5-commands.js for a basic Discord music bot with 5 commands (~200 lines).
Commands: !play, !stop, !pause, !resume, !skip
See advanced-bot.js for a feature-rich bot with 19 commands (~900 lines).
Features:
- Rich Discord embeds with colors, thumbnails, fields
- Interactive search with 5-result selection (30s timeout)
- Queue pagination with Previous/Next buttons
- Progress bar visualization with time tracking
- Advanced queue control (playNext, move, swap, jump)
- Comprehensive statistics and queue info
- Full error handling with helpful messages
Commands:
- Playback (6): play, search, skip, pause, resume, stop
- Queue (5): queue, shuffle, clear, remove, nowplaying
- Advanced (4): playnext, move, swap, jump
- Info (4): stats, queueinfo, upcoming, help
New in v3.1.0:
- Advanced Bot Guide - Complete command reference and setup
- Examples Comparison - Compare simple vs advanced bots
- Visual Guide - Diagrams and visual examples
- Examples Index - Master navigation guide
See node-example.js for using lavaflow without Discord.js.
🤖 Expand AutoPlay Details
lavaflow includes intelligent AutoPlay that automatically finds and plays related tracks when your queue ends:
- YouTube: Uses YouTube's recommendation system (RD playlists)
- SoundCloud: Searches for similar tracks by artist/title
- Spotify: Uses multi-step recommendation strategy with quality filtering
sprec:<trackId>attempt (if backend supports it)- title + artist matching across Spotify / YouTube Music / YouTube
- invalid low-quality results (e.g. numeric-only titles) are filtered
AutoPlay keeps track of the last 50 played tracks to avoid repetition.
const manager = new Manager({
autoPlay: true, // Enable AutoPlay (default: true)
debug: true // Show debug logs (default: false)
});🔌 Expand Plugin System
lavaflow supports custom plugins to extend functionality:
import { Manager, LavaPlugin } from 'lavaflow';
const myPlugin: LavaPlugin = {
name: 'MyPlugin',
onLoad(manager) {
console.log('Plugin loaded!');
},
onEvent(event, ...args) {
if (event === 'trackStart') {
console.log('Track started:', args);
}
}
};
manager.use(myPlugin);Check out lava-plugin-voice-status for a voice channel status plugin example.
📖 Expand API Documentation
// Add track to front of queue (play next)
player.playNext(track): boolean;
// Reorder queue positions
player.moveTrack(fromIndex: number, toIndex: number): boolean;
// Swap two queue positions
player.swapTracks(indexA: number, indexB: number): boolean;
// Jump to track and play immediately
await player.jumpTo(index: number): Promise<boolean>;
// Get queue summary
player.getQueueInfo(): {
length: number;
totalDurationMs: number;
nowPlaying: Track | null;
upcomingSample: Track[];
};
// Peek at next N tracks
player.getUpcoming(count?: number): Track[];
// Get player metrics
player.getMetrics(): {
tracksPlayed: number;
errorCount: number;
totalPlaybackTime: number;
sessionDuration: number;
uptime: number;
avgTracksPerMinute: number;
};
// Get history statistics
player.getHistoryStats(): {
skipped: number;
unique: number;
repeated: number;
};interface ManagerOptions {
nodes: NodeOptions[];
send: (guildId: string, payload: DiscordVoicePayload) => void;
clientId?: string;
shards?: number;
autoPlay?: boolean;
defaultSearchPlatform?: 'ytsearch' | 'ytmsearch' | 'scsearch' | 'spsearch' | 'amsearch';
debug?: boolean;
maxQueueSize?: number;
apiRateLimitDelay?: number;
healthCheckInterval?: number;
autoMovePlayersOnNodeDisconnect?: boolean;
dave?: {
enabled?: boolean;
maxProtocolVersion?: number;
minLavalinkVersion?: string;
};
}manager.getNodeVersions(): Promise<Map<string, string>>;
manager.getNodeVersion(name: string): Promise<string>;
manager.getSessionPlayers(nodeName: string): Promise<PlayerResponse[]>;
manager.getDaveReadinessReport(): Promise<DaveReadinessReport>;
manager.runStartupDiagnostics(): Promise<StartupDiagnosticReport>;interface NodeOptions {
name: string;
host: string;
port: number;
password: string;
secure?: boolean;
resumeKey?: string;
resumeTimeout?: number;
maxReconnectAttempts?: number;
reconnectDelay?: number;
region?: string; // Geographic region for voice optimization
retryStrategy?: 'exponential' | 'linear' | 'none'; // Rate limiting strategy
}interface PlayerOptions {
guildId: string;
voiceChannelId: string;
textChannelId?: string;
selfDeafen?: boolean;
selfMute?: boolean;
volume?: number;
}- Singleton: NodeManager for centralized node management
- Factory: Player creation through Manager
- Builder: Fluent API for filter configuration
- Event-Driven: Comprehensive event system for reactive patterns
- Automatic reconnection with exponential backoff
- Jitter to prevent thundering herd
- Node health monitoring
- Graceful degradation
MIT © ryxu-xo
Contributions are welcome! Please read our contributing guidelines and code of conduct.