Skip to main content

Discord Stem Separation Bot

~30 min

Build a bot for your music community

What You'll Build

A Discord bot that lets users separate stems from audio files with simple slash commands. Perfect for music production servers, remix communities, and DJ groups.

/separate
Slash commands
All Stems
Vocals, drums, bass, etc.
Community Ready
Multi-user support

How It Works

User:/separate vocals+ attached audio file
Bot:๐ŸŽต Processing "song.mp3"... (estimated: 45 seconds)
Bot:โœ… Done! Here are your stems:
๐Ÿ“Ž vocals.mp3
๐Ÿ“Ž instrumental.mp3

Prerequisites

  • Node.js 18+ installed
  • StemSplit API key (get one here)
  • Discord account with a server to test
1

Create Discord Application

  1. Go to Discord Developer Portal
  2. Click "New Application" and give it a name (e.g., "StemSplit Bot")
  3. Go to the "Bot" tab and click "Add Bot"
  4. Copy the Bot Token (keep this secret!)
  5. Under "Privileged Gateway Intents", enable:
    • Message Content Intent
  6. Go to "OAuth2" โ†’ "URL Generator", select:
    • Scopes: bot, applications.commands
    • Bot Permissions: Send Messages, Attach Files, Use Slash Commands
  7. Use the generated URL to invite the bot to your server
2

Set Up Your Project

Terminal
# Create project directory
mkdir stemsplit-discord-bot && cd stemsplit-discord-bot

# Initialize npm project
npm init -y

# Install dependencies
npm install discord.js axios dotenv

# Create files
touch index.js .env
.env
DISCORD_TOKEN=your_discord_bot_token_here
STEMSPLIT_API_KEY=sk_live_your_api_key_here
3

Build the Bot

Here's the complete bot code with slash commands:

index.js
require('dotenv').config();
const { Client, GatewayIntentBits, SlashCommandBuilder, REST, Routes, AttachmentBuilder } = require('discord.js');
const axios = require('axios');

const client = new Client({
  intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages]
});

const STEMSPLIT_API = 'https://stemsplit.io/api/v1';

// Register slash commands
const commands = [
  new SlashCommandBuilder()
    .setName('separate')
    .setDescription('Separate stems from an audio file')
    .addAttachmentOption(option =>
      option.setName('audio')
        .setDescription('The audio file to process')
        .setRequired(true))
    .addStringOption(option =>
      option.setName('type')
        .setDescription('What to extract')
        .setRequired(true)
        .addChoices(
          { name: 'Vocals Only', value: 'VOCALS' },
          { name: 'Instrumental Only', value: 'INSTRUMENTAL' },
          { name: 'All Stems (vocals, drums, bass, other)', value: 'ALL_STEMS' }
        ))
].map(cmd => cmd.toJSON());

// Deploy commands on startup
client.once('ready', async () => {
  console.log(`โœ… Logged in as ${client.user.tag}`);
  
  const rest = new REST({ version: '10' }).setToken(process.env.DISCORD_TOKEN);
  await rest.put(Routes.applicationCommands(client.user.id), { body: commands });
  console.log('โœ… Slash commands registered');
});

// Handle slash commands
client.on('interactionCreate', async interaction => {
  if (!interaction.isChatInputCommand()) return;
  if (interaction.commandName !== 'separate') return;

  const attachment = interaction.options.getAttachment('audio');
  const outputType = interaction.options.getString('type');

  // Validate file
  const validTypes = ['audio/mpeg', 'audio/wav', 'audio/flac', 'audio/mp4', 'video/mp4'];
  if (!validTypes.some(t => attachment.contentType?.includes(t))) {
    return interaction.reply({ content: 'โŒ Please upload an audio file (MP3, WAV, FLAC)', ephemeral: true });
  }

  // Check file size (25MB Discord limit, but let's be safe)
  if (attachment.size > 20 * 1024 * 1024) {
    return interaction.reply({ content: 'โŒ File too large. Max 20MB.', ephemeral: true });
  }

  await interaction.deferReply();

  try {
    // Create job with source URL (Discord CDN)
    const jobResponse = await axios.post(`${STEMSPLIT_API}/jobs`, {
      sourceUrl: attachment.url,
      outputType: outputType,
      quality: 'BEST',
      outputFormat: 'MP3'
    }, {
      headers: { 'Authorization': `Bearer ${process.env.STEMSPLIT_API_KEY}` }
    });

    const jobId = jobResponse.data.id;
    await interaction.editReply(`๐ŸŽต Processing "${attachment.name}"... This may take a minute.`);

    // Poll for completion
    let job;
    while (true) {
      await new Promise(r => setTimeout(r, 5000)); // Wait 5 seconds
      
      const statusResponse = await axios.get(`${STEMSPLIT_API}/jobs/${jobId}`, {
        headers: { 'Authorization': `Bearer ${process.env.STEMSPLIT_API_KEY}` }
      });
      
      job = statusResponse.data;
      
      if (job.status === 'COMPLETED') break;
      if (job.status === 'FAILED') {
        return interaction.editReply(`โŒ Processing failed: ${job.error || 'Unknown error'}`);
      }
    }

    // Download and send stems
    const files = [];
    for (const [stemName, stemData] of Object.entries(job.outputs)) {
      const response = await axios.get(stemData.url, { responseType: 'arraybuffer' });
      files.push(new AttachmentBuilder(Buffer.from(response.data), { name: `${stemName}.mp3` }));
    }

    await interaction.editReply({
      content: `โœ… Done! Here are your stems for "${attachment.name}":`,
      files: files
    });

  } catch (error) {
    console.error('Error:', error.response?.data || error.message);
    await interaction.editReply(`โŒ Error: ${error.response?.data?.error || error.message}`);
  }
});

client.login(process.env.DISCORD_TOKEN);
4

Run Your Bot

Terminal
node index.js

You should see:

โœ… Logged in as StemSplit Bot#1234
โœ… Slash commands registered

Now go to your Discord server and try /separate!

5

Deploy for 24/7 Uptime

For production, deploy your bot to a hosting service:

Railway (Recommended)

Free tier available. Just connect your GitHub repo and set environment variables.

railway.app โ†’

Render

Free tier with automatic deploys from Git.

render.com โ†’

Your Own VPS

Use PM2 to keep the bot running: pm2 start index.js --name stemsplit-bot

๐Ÿš€ Enhancement Ideas

YouTube Support

Use our YouTube API to let users paste YouTube links

User Credit System

Track usage per user with a database like SQLite

Queue System

Handle multiple requests with a job queue

Webhooks

Use webhooks instead of polling for faster responses

Tips for Music Servers

  • โ€ข Set up a dedicated #stem-separation channel to keep things organized
  • โ€ข Add rate limiting to prevent abuse (e.g., 5 requests per user per hour)
  • โ€ข Consider a "premium" role for unlimited access funded by Patreon
  • โ€ข Credit usage: ~1 credit per second of audio processed