Guide
Discord slash commands not showing up
Writing a handler for a slash command does not make it appear. Discord only shows commands that have been registered with it, in places the bot is allowed to show them. Here is how to find the missing part.
← All guides3 min readUpdated
Handlers are not registrations
A slash command has two halves. The definition — its name, description and options — is sent to Discord and stored there, and it is what puts the command in the / menu. The handler is the code in your bot that runs when somebody uses it.
So the symptom tells you which half is wrong. If the command is missing from the menu, its definition never reached Discord, or reached it somewhere you are not looking. If the command appears but using it says "The application did not respond", it is registered and the handler is the problem: it threw, or took longer than three seconds to answer.
Registering in discord.js
discord.js does not register commands for you. The usual pattern is a small deploy script, separate from the bot, that sends every definition to Discord in one request. That request replaces the whole set, so a command left out of the list is removed.
Run it when your commands change, not every time the bot starts. Your application ID is on the General Information page of the Developer Portal; a server's ID is in its right-click menu once Developer Mode is on in Discord's Advanced settings.
deploy-commands.mjs
import { REST, Routes, SlashCommandBuilder } from 'discord.js';
const commands = [
new SlashCommandBuilder().setName('ping').setDescription('Replies with pong'),
].map((command) => command.toJSON());
const rest = new REST().setToken(process.env.DISCORD_TOKEN);
// While developing: one server, where changes show at once.
await rest.put(Routes.applicationGuildCommands(process.env.CLIENT_ID, process.env.GUILD_ID), { body: commands });
// When it is ready: every server the bot is in.
// await rest.put(Routes.applicationCommands(process.env.CLIENT_ID), { body: commands });Registering in discord.py
In discord.py, commands added to bot.tree exist only inside your program until the tree is synced. A bot that never calls sync has slash commands nobody can see. Commands made with @bot.command are prefix commands, not slash commands, and never appear in the menu at all.
setup_hook runs once, before the bot connects, which makes it a better place to sync than on_ready: on_ready can fire again after every reconnect. Once your commands settle, many bots stop syncing on startup and sync from an owner-only command instead, since every sync is a request against Discord's rate limits.
discord.py
import os
import discord
from discord.ext import commands
GUILD = discord.Object(id=int(os.environ["GUILD_ID"]))
class Bot(commands.Bot):
async def setup_hook(self):
# While developing: copy the commands to one server, where they show at once.
self.tree.copy_global_to(guild=GUILD)
await self.tree.sync(guild=GUILD)
# When it is ready, sync globally instead: await self.tree.sync()
bot = Bot(command_prefix="!", intents=discord.Intents.default())
@bot.tree.command(description="Replies with pong")
async def ping(interaction: discord.Interaction):
await interaction.response.send_message("pong")
bot.run(os.environ["DISCORD_TOKEN"])Guild or global
Commands can be registered for one server (a guild command) or for every server the bot is in (a global command). Guild commands show up in that server straight away, which is why both examples above use them while you are developing. Global commands also work in DMs with the bot, and can take a little while to reach every server.
Registering the same command both ways makes it appear twice in the server you developed in. When you switch to global commands, clear the guild copies: send an empty list to the guild route in discord.js, or call tree.clear_commands(guild=GUILD) and then tree.sync(guild=GUILD) in discord.py.
The invite and permissions
If the commands appear in one server but not another, look at that server. A bot added with an old invite link that only had the bot scope may not be able to show commands there. Inviting it again with both the bot and applications.commands scopes fixes it without removing the bot or its roles.
Server admins can also hide a bot's commands from some roles and channels under Server Settings → Integrations, and a command registered with default member permissions is hidden from members who lack them. If a command shows for you but not for a friend, permissions are the likely cause.
Then reload Discord
The Discord app keeps its own copy of each server's command list. After registering, press Ctrl+R (Cmd+R on a Mac) in the desktop app to reload it, or close and reopen the mobile app. A surprising number of "my commands are not showing" problems end here.
On our hosting
Registration is stored by Discord, not by your program, so the deploy script does not have to run where the bot does. Running it from your own machine whenever your commands change is the simplest arrangement; the bot on its server only needs the handlers.
If you register on startup instead, as the discord.py example does, the console shows anything Discord rejects, such as a 50035 Invalid Form Body for a malformed option. Put the application and server IDs in environment variables beside your token, so the same code works on your machine and on the server.
FAQ
Questions
Why does my command say "The application did not respond"?
The command is registered; your handler did not reply within three seconds. Answer straight away, or defer the reply first if the work takes longer. If it threw an error, the bot's output will show it.
How do I delete old slash commands?
Register the list you want to keep. Registering replaces the whole set for that scope, so anything not in the list is removed. An empty list removes them all.
How many slash commands can a bot have?
Up to 100 top-level slash commands globally, and another 100 per server. Subcommands do not count against that, so grouping related commands under one name is the way past it.
Read next