Commands

Slash commands are the primary way users interact with your Discord bot. They appear in Discord’s command menu and can accept various types of options.

Commands are automatically registered when placed in src/interactions/commands/. The command name is derived from the file path, so src/interactions/commands/ping.ts becomes /ping.

Creating a Command

Commands in djs-core are created using the Command class. Each command file in src/interactions/commands/ automatically becomes a slash command.

Basic Command

Here’s a simple ping command:

import { Command } from "@djs-core/runtime";
export default new Command()
.setDescription("Ping the bot")
.run(async (interaction) => {
await interaction.reply("Pong!");
});

The command name is automatically derived from the file path:

File Path Command Name
src/interactions/commands/ping.ts /ping
src/interactions/commands/admin/kick.ts /admin kick
src/interactions/commands/shop/buy.ts /shop buy
src/interactions/commands/admin/moderation/ban.ts /admin moderation ban

Command Options

Commands can accept various types of options to collect user input. Each option type serves a specific purpose:

  • String: Text input - for names, descriptions, messages, etc.
  • Integer/Number: Numeric input - for quantities, IDs, timestamps, etc.
  • Boolean: True/false checkbox - for toggles and flags
  • User: Discord user selection - for mentions and user-specific actions
  • Channel: Discord channel selection - for channel configuration
  • Role: Discord role selection - for role assignment
  • Mentionable: User or role selection - flexible mention option
  • Attachment: File upload - for images, documents, etc.

String Option

import { Command } from "@djs-core/runtime";
export default new Command()
.setDescription("A command with options")
.addStringOption((option) =>
option
.setName("text")
.setDescription("Enter some text")
.setRequired(true),
)
.run(async (interaction) => {
const text = interaction.options.getString("text");
await interaction.reply(`You entered: ${text}`);
});

Autocomplete

Commands can provide autocomplete suggestions for string, integer, and number options. This improves user experience by showing relevant options as they type.

Autocomplete is especially useful for commands with many possible values, like searching for items, selecting from a list, or choosing from predefined options.

Use .autocomplete(optionName, fn) to declare a handler per option. The function receives the current input value and the interaction — no need to call getFocused() or respond() manually:

import { Command } from "@djs-core/runtime";
const FRUITS = ["apple", "banana", "cherry", "date", "elderberry"];
export default new Command()
.setDescription("Pick a fruit")
.addStringOption((option) =>
option
.setName("fruit")
.setDescription("The fruit to pick")
.setRequired(true)
.setAutocomplete(true),
)
.autocomplete("fruit", (value) =>
FRUITS
.filter((f) => f.startsWith(value))
.map((f) => ({ name: f, value: f })),
)
.run(async (interaction) => {
const fruit = interaction.options.getString("fruit", true);
await interaction.reply(`You picked: ${fruit}`);
});

Multiple autocomplete options

Each option gets its own .autocomplete() handler — they are matched by option name automatically:

import { Command } from "@djs-core/runtime";
const FRUITS = ["apple", "banana", "cherry"];
const COLORS = ["red", "green", "blue"];
export default new Command()
.setDescription("Pick a fruit and a color")
.addStringOption((option) =>
option.setName("fruit").setDescription("A fruit").setAutocomplete(true),
)
.addStringOption((option) =>
option.setName("color").setDescription("A color").setAutocomplete(true),
)
.autocomplete("fruit", (value) =>
FRUITS.filter((f) => f.startsWith(value)).map((f) => ({ name: f, value: f })),
)
.autocomplete("color", (value) =>
COLORS.filter((c) => c.startsWith(value)).map((c) => ({ name: c, value: c })),
)
.run(async (interaction) => {
const fruit = interaction.options.getString("fruit", true);
const color = interaction.options.getString("color", true);
await interaction.reply(`${fruit} in ${color}`);
});

Accessing the interaction

The handler also receives the full interaction as a second argument, useful when you need the client (e.g. to query a database):

.autocomplete("query", async (value, interaction) => {
const results = await interaction.client.drizzle
.select()
.from(products)
.where(like(products.name, `${value}%`));
return results.map((r) => ({ name: r.name, value: String(r.id) }));
})

Low-level fallback

For complex cases where you need full control, .runAutocomplete() gives you the raw interaction and lets you call getFocused() and respond() yourself. Note that .autocomplete() handlers take priority — .runAutocomplete() is only called when no per-option handler matches:

.runAutocomplete(async (interaction) => {
const focused = interaction.options.getFocused(true);
// custom logic across multiple fields...
await interaction.respond([{ name: "custom", value: "result" }]);
})

Permissions

You can restrict commands to users with specific permissions. This is essential for administrative or sensitive commands.

Users without the required permissions won’t see the command in Discord’s command menu at all.

import { Command } from "@djs-core/runtime";
import { PermissionFlagsBits } from "discord.js";
export default new Command()
.setDescription("Restart the bot")
.setDefaultMemberPermissions(PermissionFlagsBits.Administrator)
.run(async (interaction) => {
await interaction.reply("Bot restarting...");
// Restart logic here
});