Browse documentation

Adding slash commands

Add slash commands to your Discord bot on FPS.ms with Python (discord.py) or Node.js (discord.js). Register, handle, and deploy commands step by step.

CanerAkar Updated
On this page

Slash commands are the modern way for users to interact with Discord bots. When a user types / in a Discord channel, they see a list of available commands with descriptions and parameters. This guide shows you how to add slash commands to your bot hosted on FPS.ms.

Why use slash commands?

Feature Slash commands Message-based commands
Discoverability Users see all commands by typing / Users must know the prefix and command name
Permissions Built-in Discord permission system Must be coded manually
Parameters Typed parameters with validation Must parse text manually
Message Content intent Not required Required (privileged intent)

Recommended for new bots

Discord recommends slash commands for all new bots. They don't require the Message Content privileged intent, making your bot easier to verify and approve.

Prerequisites

Before adding slash commands, make sure you:

  1. Have your bot installed and running on FPS.ms
  2. Added the applications.commands scope when inviting your bot
  3. Have your bot token set up as an environment variable

Missing applications.commands scope?

If you didn't include applications.commands when inviting your bot, slash commands won't work. Re-invite the bot using the OAuth2 URL Generator in the Discord Developer Portal with both bot and applications.commands scopes selected.

Python (discord.py)

Discord.py uses a commands.Bot with a command tree to handle slash commands.

Basic slash command

app.py
import os
from dotenv import load_dotenv
import discord
from discord import app_commands

load_dotenv()

class MyBot(discord.Client):
    def __init__(self):
        super().__init__(intents=discord.Intents.default())
        self.tree = app_commands.CommandTree(self)

    async def setup_hook(self):
        await self.tree.sync()
        print(f'Synced {len(self.tree.get_commands())} commands')

bot = MyBot()

@bot.tree.command(name='ping', description='Check if the bot is online')
async def ping(interaction: discord.Interaction):
    await interaction.response.send_message(f'Pong! Latency: {round(bot.latency * 1000)}ms')

@bot.tree.command(name='hello', description='Get a greeting from the bot')
@app_commands.describe(name='The name to greet')
async def hello(interaction: discord.Interaction, name: str = None):
    if name:
        await interaction.response.send_message(f'Hello, {name}!')
    else:
        await interaction.response.send_message(f'Hello, {interaction.user.display_name}!')

bot.run(os.environ['BOT_TOKEN'])

How it works

  1. CommandTree — manages all your slash commands
  2. @bot.tree.command() — registers a new slash command with a name and description
  3. @app_commands.describe() — adds descriptions to command parameters
  4. setup_hook() — syncs commands with Discord when the bot starts
  5. interaction.response.send_message() — replies to the user

Node.js (discord.js)

Discord.js uses a two-step process: register commands via the REST API, then handle them in your bot code.

Step 1: Register commands

Create a separate script to register your commands with Discord:

deploy-commands.js
require('dotenv').config();
const { REST, Routes, SlashCommandBuilder } = require('discord.js');

const commands = [
    new SlashCommandBuilder()
        .setName('ping')
        .setDescription('Check if the bot is online'),
    new SlashCommandBuilder()
        .setName('hello')
        .setDescription('Get a greeting from the bot')
        .addStringOption(option =>
            option.setName('name')
                .setDescription('The name to greet')
                .setRequired(false)),
].map(command => command.toJSON());

const rest = new REST().setToken(process.env.BOT_TOKEN);

(async () => {
    try {
        console.log('Registering slash commands...');
        await rest.put(
            Routes.applicationCommands(process.env.CLIENT_ID),
            { body: commands },
        );
        console.log('Slash commands registered!');
    } catch (error) {
        console.error(error);
    }
})();

Run this script once to register your commands:

Bash
node deploy-commands.js

Set CLIENT_ID

Add CLIENT_ID=your-app-id to your .env file on FPS.ms. Find it in the Discord Developer Portal under your application's General Information page (called Application ID). See the environment variables guide for details.

Step 2: Handle commands

index.js
require('dotenv').config();
const { Client, Events, GatewayIntentBits } = require('discord.js');

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

client.on(Events.InteractionCreate, async interaction => {
    if (!interaction.isChatInputCommand()) return;

    if (interaction.commandName === 'ping') {
        await interaction.reply(`Pong! Latency: ${Math.round(client.ws.ping)}ms`);
    }

    if (interaction.commandName === 'hello') {
        const name = interaction.options.getString('name');
        if (name) {
            await interaction.reply(`Hello, ${name}!`);
        } else {
            await interaction.reply(`Hello, ${interaction.user.displayName}!`);
        }
    }
});

client.once(Events.ClientReady, readyClient => {
    console.log(`Ready! Logged in as ${readyClient.user.tag}`);
});

client.login(process.env.BOT_TOKEN);

Global vs guild commands

Type Registration time Scope Best for
Global Up to 1 hour All servers the bot is in Production bots
Guild Instant One specific server Development and testing

For faster testing, register commands to a specific guild. In Python:

Python
MY_GUILD = discord.Object(id=int(os.environ['GUILD_ID']))

async def setup_hook(self):
    self.tree.copy_global_to(guild=MY_GUILD)
    await self.tree.sync(guild=MY_GUILD)

In Node.js, change the Routes in deploy-commands.js:

JavaScript
// Global (slow, production)
Routes.applicationCommands(process.env.CLIENT_ID)

// Guild-specific (instant, development)
Routes.applicationGuildCommands(process.env.CLIENT_ID, process.env.GUILD_ID)

Troubleshooting

Problem Cause Fix
Commands not showing up Not synced or registration delay For global commands, wait up to 1 hour. For guild commands, check the guild ID.
Missing Access error Bot missing applications.commands scope Re-invite the bot with the correct scopes
Unknown interaction Bot took too long to respond (>3 seconds) Use interaction.deferReply() for slow operations
Duplicate commands Synced both global and guild copies Remove guild commands if using global, or vice versa

For more help, see our full troubleshooting guide.

Next steps