Lunacord

Recipes

Copy-paste-ready patterns for common music bot features.

Role-gate /skip

music.commands.extend("skip", async (ctx, next) => {
  const member = ctx.interaction.member;
  if (!member || !("permissions" in member)) return ctx.error("Members only.");
  const hasRole = member.roles.cache.some((r) => r.name === "DJ");
  if (!hasRole) return ctx.error("DJ role required.");
  return next();
});

Persist + rehydrate

const music = MusicKit.create(client, {
  nodes,
  persistence: new RedisPersistenceAdapter(redis),
  // autoRehydrate is on by default when persistence is set
});

Shared autoplay

lunacord.use(
  createAutoplayPlugin(
    { name: "autoplay", version: "1.0.0" },
    {
      next: async ({ lastTrack }) =>
        lastTrack ? `${lastTrack.author} mix` : "lofi hip hop",
      searchProvider: "ytmsearch",
    }
  )
);

Brand the embeds

MusicKit.create(client, {
  nodes,
  embeds: {
    ...defaultEmbedFactory,
    nowPlaying(player) {
      return new EmbedBuilder().setTitle(player.current?.title ?? "—").setColor(0xff69b4);
    },
  },
});

Deploy slash commands to one guild for testing

await music.commands.installDefaults({ scope: "guild", guildId: process.env.GUILD_ID });

Subscribe to the unified debug event

music.lunacord.on("debug", ({ scope, message, data, nodeId }) => {
  console.log(`[${scope}${nodeId ? `:${nodeId}` : ""}] ${message}`, data ?? "");
});

On this page