Docs academy · Guide 4 of 8 · 12 min read

Slash commands, intents and permissions

Options, global commands, which events your bot receives, and how to ask only for the access it needs.


Slash command options

Options let people pass values to a command, and Discord checks the types before your code ever runs.

In discord.js:

new SlashCommandBuilder()
  .setName("remind")
  .setDescription("Set a reminder")
  .addStringOption((option) =>
    option.setName("text").setDescription("What to remind you about").setRequired(true),
  )
  .addIntegerOption((option) =>
    option.setName("minutes").setDescription("How long to wait").setMinValue(1).setMaxValue(1440).setRequired(true),
  );

// In your interaction handler:
const text = interaction.options.getString("text", true);
const minutes = interaction.options.getInteger("minutes", true);

In discord.py:

@bot.tree.command(description="Set a reminder")
@app_commands.describe(text="What to remind you about", minutes="How long to wait")
async def remind(interaction: discord.Interaction, text: str, minutes: app_commands.Range[int, 1, 1440]):
    await interaction.response.send_message(f"I'll remind you in {minutes} minute(s).", ephemeral=True)

ephemeral means only the person who ran the command sees the reply, which suits confirmations and errors.

Guild commands and global commands

The previous guides registered commands in your test server only. These are guild commands: they update instantly, so they're ideal while developing.

For a public bot you need global commands, which work in every server the bot joins.

In discord.js, change the route in deploy-commands.js:

await rest.put(Routes.applicationCommands(process.env.DISCORD_CLIENT_ID), { body: commands });

In discord.py, sync without a guild:

await self.tree.sync()

Both replace your full list of commands, so always send all of them. Sync only when commands change, because Discord rate limits this call. Don't do it on every restart of a live bot.

If you keep both, remove the old guild copies from your test server, or you'll see every command listed twice there.

Intents

Intents decide which events Discord sends you. Asking for less means less traffic and less private data passing through your bot.

Three intents are privileged. You must switch them on for your bot on the Bot page of the Developer Portal, and once your bot is in 100 or more servers Discord must approve them:

| Intent | What it gives you | Do you need it? | | --- | --- | --- | | Message Content | The text of messages | Not if you use slash commands. It's the most commonly requested and most often unneeded. | | Server Members | Member joins, leaves and full member lists | Welcome bots and member-based leveling | | Presence | Who is online and what they're doing | Rarely |

In discord.js:

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

In discord.py:

intents = discord.Intents.default()
intents.members = True

Request an intent only when a feature really needs it. Verification (guide 7) asks you to justify each privileged intent, and "I might use it later" won't pass.

Permissions

Permissions decide what your bot can do in a server. They're combined into one number, which is the permissions= value in your invite link. 2048 is Send Messages. 3072 is View Channels plus Send Messages.

Nexobots has a visual picker that builds this number for you when you list your bot, and the Developer Portal has a calculator on its OAuth2 page.

Ask for the fewest permissions that work. Server owners read the invite screen, and many reasonably refuse a bot that asks for more than it needs. In particular:

  • Avoid Administrator. It bypasses every permission check in the server. A bot with Administrator and a leaked token can destroy a server in seconds. On Nexobots it's only allowed as an exception: you explain why on your listing, and a Moderator or Administrator decides.
  • Add permissions as features need them, and say in your description why you need the unusual ones.

Check before you act

A server can take a permission away from your bot at any time, so handle that politely instead of failing with an error:

import { PermissionFlagsBits } from "discord.js";

if (!interaction.appPermissions.has(PermissionFlagsBits.ManageMessages)) {
  return interaction.reply({ content: "I need the Manage Messages permission to do that.", ephemeral: true });
}
if not interaction.app_permissions.manage_messages:
    await interaction.response.send_message("I need the Manage Messages permission to do that.", ephemeral=True)
    return

Check who is running the command

Your bot's permissions aren't the user's. Without a check, anyone could use a moderation command through your bot. Set a default so the command is hidden from people who lack the permission:

new SlashCommandBuilder()
  .setName("purge")
  .setDescription("Delete recent messages")
  .setDefaultMemberPermissions(PermissionFlagsBits.ManageMessages)
  .setDMPermission(false);
@bot.tree.command(description="Delete recent messages")
@app_commands.default_permissions(manage_messages=True)
@app_commands.guild_only()
async def purge(interaction: discord.Interaction):
    ...

Server admins can override these defaults, which is by design. For anything dangerous, also check the user's permissions inside the command itself.

Next

Your bot forgets everything when it restarts. Fix that with Storing data.

Still stuck? Open a ticket and a mentor will take a look. We'll know which guide you were on.