Getting Started
Build a music bot with YuKumo in a few minutes.
Build a music bot with YuKumo in a few minutes.
Prerequisites
- Node.js 18+ or Bun 1.0+
- A Lavalink v4 server running and accessible
- A Discord bot token with
GuildVoiceStatesandGuildMessagesintents
No Lavalink server?
See the Node Deployment guide for quick Docker setup.
Installation
bun add yukumoYuKumo has minimal runtime dependencies (only ws) — lightweight and fast.
Works with both TypeScript and JavaScript (ESM + CommonJS).
1. Create the YuKumo instance
import { YuKumo } from "yukumo";
const client = new YuKumo({
nodes: [
{
host: "localhost",
port: 2333,
password: "youshallnotpass",
name: "primary",
},
],
});// CommonJS
const { YuKumo } = require("yukumo");2. Initialize
await client.init();This connects all Lavalink nodes, starts plugins, and prepares the player system.
3. Forward voice events
YuKumo needs voice state data from Discord to establish connections. You can either use an adapter (recommended) or forward events manually.
Option A: Use Framework Adapters (recommended)
import { YuKumo, DiscordJSAdapter } from "yukumo";
const yukumo = new YuKumo({ nodes: [...] });
const adapter = new DiscordJSAdapter(discordClient, yukumo);The adapter handles all voice state and server update forwarding automatically.
Option B: Manual forwarding
client.on("voiceStateUpdate", (oldState, newState) => {
client.handleVoiceStateUpdate({
guildId: newState.guild.id,
sessionId: newState.sessionId ?? "",
channelId: newState.channelId,
userId: newState.id,
});
});
client.on("voiceServerUpdate", (data) => {
if (!data.endpoint) return;
client.handleVoiceServerUpdate(data.guildId, {
token: data.token,
endpoint: data.endpoint,
});
});Required
Without forwarding both events, YuKumo cannot establish a voice connection. This is the most common setup mistake.
4. Create a player
const player = await client.createPlayer({
guildId: "123456789",
voiceChannelId: "987654321",
textChannelId: "111111111",
selfDeaf: true,
selfMute: false,
});If a player already exists for this guild, the existing instance is returned.
5. Search and play
const result = await client.search("never gonna give you up");
if (result.tracks.length > 0) {
await client.play("123456789", result.tracks[0]);
console.log(`Playing: ${result.tracks[0].info.title}`);
}Supports any Lavalink source: ytsearch:, scsearch:, spsearch:, direct URLs, and more.
Player controls
| Method | Description |
|---|---|
client.pause(guildId) | Pause playback |
client.resume(guildId) | Resume playback |
client.stop(guildId) | Stop and clear queue |
client.skip(guildId) | Skip to next track |
client.setVolume(guildId, 100) | Set volume (0–1000) |
client.destroyPlayer(guildId) | Destroy player and leave voice |
await client.pause("123456789");
await client.setVolume("123456789", 80);Events
Subscribe to real-time events from Lavalink:
client.on("trackStart", (guildId, track) => {
console.log(`Now playing: ${track.info.title}`);
});
client.on("trackEnd", (guildId, track, reason) => {
console.log(`Track ended: ${reason}`);
});
client.on("queueEnd", (guildId) => {
console.log(`Queue empty in ${guildId}`);
});
client.on("nodeReady", (nodeId) => {
console.log(`Node connected: ${nodeId}`);
});
client.on("nodeDisconnected", (nodeId, code, reason) => {
console.warn(`Node disconnected: ${reason}`);
});
client.on("voiceReady", (guildId) => {
console.log(`Voice ready in ${guildId}`);
});
client.on("voiceDisconnected", (guildId) => {
console.log(`Voice disconnected from ${guildId}`);
});Clean shutdown
process.on("SIGINT", async () => {
await client.destroy(); // destroys players, disconnects nodes, stops plugins
process.exit(0);
});Complete examples
See working bots in the examples directory: