My Bot's Slash Commands Aren't Showing: Syncing in discord.py and discord.js
A slash command doesn't appear until your bot publishes it to Discord: defining it in your code isn't enough. In discord.py you publish with tree.sync() and in discord.js with rest.put(Routes.applicationCommands…). To test, publish to your own server, where it shows up straight away; and if you still don't see it, check the applications.commands scope and the permissions.
Why they don't appear: they have to be published
Discord stores each bot's list of commands. Your code defines the commands and handles them, but the list only changes when the bot sends it to Discord (syncing, or registering). If you never send it, or send it before defining the commands, nothing shows up on Discord when you type /.
You only need to publish them once, and again whenever you change a command's name, description or options. Changing what it does inside doesn't need republishing. Watch the limit: Discord lets you create 200 commands a day per server.
Rather not set it up yourself? Discord bot hosting from €1.50 a month, no contract and a full refund within 72 hours. Or create a free Discord bot, no card needed.
Global or per server: how long they take
There are two ways to publish a command. Global: it shows up on every server your bot is in (and in its direct messages). Per server (guild): only on that server, and not in direct messages.
Per-server commands show up straight away, according to Discord's documentation, which recommends using them for testing and switching to global when they're ready. For global ones, Discord's documentation no longer gives a timeframe; the official discord.py example says they can take up to an hour to arrive. If a global command doesn't show up for you, close and reopen Discord before changing anything.
If you publish the same command as global and on your server, both show up on your server (Discord allows it). When you switch to global, delete the ones on your server as explained below.
Syncing in discord.py: tree.sync()
In discord.py 2.7, slash commands live in the CommandTree (tree; with commands.Bot it already comes as bot.tree). tree.sync() publishes the global ones; tree.sync(guild=…) the ones for a server, and tree.copy_global_to(guild=…) first copies the global ones to your test server so they show up straight away.
Do the sync in setup_hook, which runs once at startup with the commands already defined. on_ready works too, but on_ready can run again on every reconnect. You copy your server's ID with Discord's Developer Mode turned on: right-click the server → “Copy Server ID”.
import os
import discord
from discord import app_commands
from dotenv import load_dotenv
load_dotenv()
MY_GUILD = discord.Object(id=int(os.environ["GUILD_ID"])) # your test server
class MyBot(discord.Client):
def __init__(self):
super().__init__(intents=discord.Intents.default())
self.tree = app_commands.CommandTree(self)
async def setup_hook(self):
# For testing: copy the commands to your server (they show up straight away)
self.tree.copy_global_to(guild=MY_GUILD)
published = await self.tree.sync(guild=MY_GUILD)
print(f"Published {len(published)} commands to your server")
# Ready for everyone: replace the two lines above with
# await self.tree.sync()
client = MyBot()
@client.tree.command(name="hello", description="Says hello")
async def hello(interaction: discord.Interaction):
await interaction.response.send_message(f"Hello, {interaction.user.mention}!")
client.run(os.environ["DISCORD_TOKEN"])Registering in discord.js: REST.put and Routes
In discord.js 14, commands are published with a request to the API: rest.put with Routes.applicationGuildCommands(application id, server id) for a server's commands, or Routes.applicationCommands(application id) for global ones. put replaces the whole list with the one you send: a command that isn't in the list gets deleted.
The official discord.js guide does it in a separate script you run only when you change the commands (“node deploy-commands.js”), not on every startup. Since it only talks to Discord's API, you can run it on your computer even if the bot is hosted somewhere else. The application id is in the portal, under “General Information” → “Application ID”.
DISCORD_TOKEN=paste-your-token-here
CLIENT_ID=the-application-id
GUILD_ID=your-server-idrequire("dotenv").config();
const { REST, Routes, SlashCommandBuilder } = require("discord.js");
const commands = [
new SlashCommandBuilder().setName("hello").setDescription("Says hello"),
].map((c) => c.toJSON());
const rest = new REST().setToken(process.env.DISCORD_TOKEN);
(async () => {
// For testing: only on your server (they show up straight away)
const route = Routes.applicationGuildCommands(process.env.CLIENT_ID, process.env.GUILD_ID);
// Ready for everyone: Routes.applicationCommands(process.env.CLIENT_ID)
const published = await rest.put(route, { body: commands });
console.log(`Published ${published.length} commands`);
})().catch(console.error);Publish the commands
With the .env filled in, run “node deploy-commands.js”. It should say “Published 1 commands”; if it gives an error, read it: it's usually the token, the id or the scope.
Handle them in the bot
Your index.js has to listen for Events.InteractionCreate and reply to each command (as in your first bot in JavaScript). Published but not handled, they appear and fail when used.
Try it with /
Type / on your server and look for your bot in the list. When it works, change the route to Routes.applicationCommands and run the script again.
Still not showing: the applications.commands scope
For a bot to have commands on a server, it has to be authorised there with the applications.commands scope. Discord's documentation says the bot scope already includes it; even so, if syncing to a server gives a Forbidden error in discord.py (its documentation puts it down to that missing scope) or error 50001 (Missing Access) in discord.js, open the invite link again with “bot” and “applications.commands” ticked. There's no need to kick the bot first. You can build the link with the bot permissions calculator.
Also check that GUILD_ID is the server you're looking at and CLIENT_ID is your application's, not another one's.
You see them but others don't: “Use Application Commands” permission
Whether someone sees your bot's commands depends on their permissions, not the bot's. If their role, or the channel, has “Use Application Commands” denied, they don't get them. Check it in the role settings and under “Edit Channel” → “Permissions”.
On top of that, server admins can limit each bot's commands by role or channel in Server Settings → “Integrations”, and your code can require permissions to use a command (default_permissions in discord.py, setDefaultMemberPermissions in discord.js): anyone without them doesn't see it. Test with an account that isn't an admin, because an admin sees them all.
Duplicate or old commands: how to delete them
If a command shows up twice, it's published both as global and on your server. If one shows up that no longer exists in your code, it's still in Discord's list. You fix it by publishing the right list again, or an empty list where there are extras:
In discord.py, to clear your server's: tree.clear_commands(guild=MY_GUILD) and then await tree.sync(guild=MY_GUILD). In discord.js: rest.put(Routes.applicationGuildCommands(CLIENT_ID, GUILD_ID), { body: [] }). Global ones are deleted the same way, with tree.sync() without that command in your code or with Routes.applicationCommands.
If your bot is on NapNodes
Publishing commands is your code's job: it works the same on your PC as hosted. In discord.py, the setup_hook above publishes when the bot starts, and its “Published N commands” appears in the panel's “Console”. In discord.js, run deploy-commands.js on your computer, or publish from the bot itself at startup as in your first bot in JavaScript. If the bot doesn't even connect, start with my Discord bot isn't responding.
Frequently asked questions
How long do slash commands take to show up?
Ones published to a server, straight away. Global ones can take longer (the official discord.py example talks of up to an hour): to test, publish them to your server.
How do I sync commands in discord.py?
With await tree.sync() (global) or await tree.sync(guild=…) (on one server), ideally inside setup_hook. With commands.Bot it's bot.tree.sync().
How do I register commands in discord.js?
With rest.put(Routes.applicationGuildCommands(clientId, guildId), { body: commands }) for one server or Routes.applicationCommands(clientId) for all of them, usually in a deploy-commands.js script you run when you change the commands.
Why are my commands showing up twice?
Because they're published as global and also on your server. Delete your server's by publishing an empty list to it.
I can see the commands but my friends can't. Why?
Permissions: their role or the channel has “Use Application Commands” denied, or the server limits your bot's commands under “Integrations”. An admin always sees them all.
Shall we set it up for you?
Discord bot hosting from €1.50 a month, never oversold: your RAM is yours alone. And if you'd rather start without paying, there's a free Discord bot, no card needed.
Free tool: Discord bot permissions calculator. Tick your bot's permissions and get the number and the invite link; or paste a number and see which permissions it grants.
Keep reading
- Discord Bot Not Responding (or Showing Offline): Causes and Fixes
Your Discord bot doesn't reply or shows as offline: token, Message Content Intent, permissions, error 429… Each error and its fix in discord.py and discord.js.
- Slash Commands in Your Discord Bot: Options, Subcommands and Permissions
Slash commands in discord.js and discord.py: options, subcommands, permissions, slow replies, autocomplete and why they don't show up.
- Discord Bot Showing Your Minecraft Server Status (Players Online)
Build a Discord bot in Python that shows whether your Minecraft server is online, how many are playing and who, with /status. Full code, free.