One of the first confusing decisions you face when building a Discord bot is this: should you register a slash command as a discord global command or as a command tied to a specific server (guild)? Both come from the same code and fire the same way, but their registration methods, update times and use cases differ quite a bit. In this article I show both approaches with real discord.js code and make it clear which one to pick and when.
What is a global command vs a guild command?
A global command is visible in every server your bot has been invited to, and even in DMs (if you allow it). You register once, and the bot offers the same command everywhere. A guild command is only visible in the specific server where you registered it; outside that server it behaves as if the command does not exist.
On the Discord API side the distinction comes from two separate endpoints:
- Global:
PUT /applications/{application.id}/commands - Guild:
PUT /applications/{application.id}/guilds/{guild.id}/commands
discord.js wraps these endpoints with the Routes.applicationCommands() and Routes.applicationGuildCommands() helpers.
The most critical difference: update time
In practice the most important thing that separates the two methods is when the command appears in the Discord client after you register it:
- Guild commands update almost instantly. They show up in the slash menu seconds after registration. That makes them ideal for development and testing.
- Global commands propagate. Discord caches them; a change usually reaches all clients quickly, but in the worst case it can take up to an hour. Adding a new command or changing its description may not appear immediately.
This single difference drives most architectural decisions: while actively developing, you do not want to rely on global and then wait for hours wondering "why isn't it showing up?"
Registering commands with discord.js (REST)
Commands are usually registered with a separate deploy-commands.js script that you run only when commands change, not every time the bot starts. Here is an example that registers to a guild (discord.js v14):
const { REST, Routes, SlashCommandBuilder } = require('discord.js');
const commands = [
new SlashCommandBuilder()
.setName('ping')
.setDescription('Shows the bot latency'),
].map(c => c.toJSON());
const rest = new REST({ version: '10' }).setToken(process.env.TOKEN);
(async () => {
await rest.put(
Routes.applicationGuildCommands(process.env.CLIENT_ID, process.env.GUILD_ID),
{ body: commands },
);
console.log('Guild commands registered.');
})();
To make the same script global, the only change is to swap the route and drop the GUILD_ID:
await rest.put(
Routes.applicationCommands(process.env.CLIENT_ID),
{ body: commands },
);
Note: the put method overwrites. The array you send becomes the entire new set of commands for that scope (global or that guild). If you leave a command out of the array, the next put deletes it. This behavior is intentional and gives you idempotent registration.
Which one should you use, and when?
As a general rule, I recommend this approach:
- Use guild commands during development. Register them in your own test server; every change appears instantly and you never wait hours for propagation.
- Use global commands for production. If the bot runs in many servers, registering commands to each guild one by one makes no sense; global reaches everywhere in a single call.
- Keep guild commands for server-specific features. Admin commands that belong to a single community, special event commands and similar things should not be global.
Many serious projects use both: user-facing commands as global, and commands that only run in the support/admin server as guild-scoped.
Common mistakes
- Duplicate commands. If you register the same command both globally and as a guild command in your test server, you will see two copies there. Clear the guild registration once testing is done (a
putwith an empty array). - "Command not showing" panic. A global change is not reflected instantly; do not declare it "broken" without actually waiting up to an hour. For fast testing, register to a guild temporarily.
- Confusing registration with listening. Registering a command (REST
put) and handling the incoming interaction (theinteractionCreateevent) are two separate jobs. Listen without registering and the command never appears; register without listening and you get "This interaction failed" when it is clicked. - Wrong
CLIENT_ID. This is the application ID, not the bot's (they match for most bots, but the application ID is the conceptually correct one).
Frequently Asked Questions
Does a global command really take an hour?
Usually it is much faster and the change shows up within minutes. However, Discord guarantees an upper bound of one hour; especially for newly added commands, caching can cause a delay. That is why it is healthier to use guild commands for testing instead of relying on global.
Can I test a command on a guild first and then make it global?
Yes, this is the most common workflow. You register to a guild during development, then run the global script when it is ready. Just remember to clear the test guild registration so you do not see duplicates.
How do I delete a command?
Sending a put with an empty array to the relevant route deletes all commands in that scope. To delete a single command you can use rest.delete() with its ID, or remove it from the array and put again.
Want to set up the command architecture for your Discord bot correctly? I plan and build the global/guild scoping, permissions and propagation strategy around your project. If you have a bot project, get in touch with me.