YuKumo

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 GuildVoiceStates and GuildMessages intents

No Lavalink server?

See the Node Deployment guide for quick Docker setup.

Installation

bun add yukumo

YuKumo 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.

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

MethodDescription
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:

Next steps

On this page