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