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:
- Have your bot installed and running on FPS.ms
- Added the
applications.commands scope when inviting your bot
- 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
CommandTree — manages all your slash commands
@bot.tree.command() — registers a new slash command with a name and description
@app_commands.describe() — adds descriptions to command parameters
setup_hook() — syncs commands with Discord when the bot starts
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:
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:
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:
// 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