Slash commands
CommandBuilder produces application command definitions matching Discord's API. Use it with the commands framework or hand the JSON to...
CommandBuilder produces application command definitions matching Discord's API. Use it with the commands framework or hand the JSON to client.createCommand / client.bulkEditCommands.
A basic command
import { CommandBuilder } from 'athena';
const cmd = new CommandBuilder('avatar', "Show a user's avatar")
.addUserOption({ name: 'user', description: 'Whose avatar', required: false });
await client.createCommand(cmd.toJSON());A builder created as new CommandBuilder(name, description) serialises to a chat-input command without any extra call.
Options
new CommandBuilder('search', 'Search the docs')
.addStringOption({ name: 'query', description: 'Text', required: true, autocomplete: true, min_length: 1, max_length: 100 })
.addIntegerOption({ name: 'page', description: 'Page', min_value: 1, max_value: 100 })
.addNumberOption({ name: 'amount', description: 'A decimal' })
.addBooleanOption({ name: 'verbose', description: 'Extra detail' })
.addUserOption({ name: 'user', description: 'A user' })
.addChannelOption({ name: 'channel', description: 'A channel', channel_types: [ChannelType.GuildText] })
.addRoleOption({ name: 'role', description: 'A role' })
.addMentionOption({ name: 'target', description: 'User or role' })
.addAttachmentOption({ name: 'file', description: 'Upload' });String, integer, and number options accept up to 25 choices:
.addStringOption({ name: 'lang', description: 'Language', choices: [
{ name: 'English', value: 'en' },
{ name: 'Espanol', value: 'es' }
]})Subcommands and groups
new CommandBuilder('settings', 'Server settings')
.addSubcommand((s) => s.setName('view').setDescription('View settings'))
.addSubcommandGroup((g) =>
g.setName('notifications').setDescription('Manage notifications')
.addSubcommand((s) => s.setName('enable').setDescription('Turn on'))
.addSubcommand((s) => s.setName('disable').setDescription('Turn off'))
);Permissions and contexts
new CommandBuilder('purge', 'Bulk delete')
.setMemberPermission(PermissionFlagsBits.ManageMessages)
.setNSFW(false)
.setContexts([InteractionContextType.Guild, InteractionContextType.BotDM])
.setIntegrationTypes([ApplicationIntegrationType.GuildInstall, ApplicationIntegrationType.UserInstall]);setMemberPermission(bits)sets the default required permission.setContexts([...])replaces the deprecatedsetDMPermission. UseGuild,BotDM,PrivateChannel.setIntegrationTypes([...])distinguishes guild installs from user installs.
Context menu commands
new CommandBuilder('Report message', '').setCommandType(ApplicationCommandType.Message);
new CommandBuilder('View profile', '').setCommandType(ApplicationCommandType.User);No options, no description text. Since March 2026, Discord allows up to 15 USER and 15 MESSAGE context menu commands per app (up from 5 each).
Entry point commands
Apps with Activities get one entry point command (the command that launches the Activity from the App Launcher):
new CommandBuilder('launch', 'Launch the game').setHandler(EntryPointCommandHandlerType.AppHandler);setHandler(handler) marks the command PRIMARY_ENTRY_POINT (type 4). Handler 1 (AppHandler): your app receives the interaction and responds, typically with interaction.launchActivity(). Handler 2 (DiscordLaunchActivity): Discord launches the Activity itself and your app receives nothing. Entry point commands are global-only and limited to 1 per app.
Localization
new CommandBuilder('ping', 'Replies with pong')
.setNameLocalizations({ 'es-ES': 'ping' })
.setDescriptionLocalizations({ 'es-ES': 'Responde pong' });Autocomplete
Mark an option autocomplete: true and implement handleAutocomplete:
async handleAutocomplete(context, interaction) {
const focused = interaction.focused();
const matches = await search(focused.value as string);
await interaction.acknowledge(matches.slice(0, 25).map((m) => ({ name: m.title, value: m.id })));
}Deploying
await client.createCommand(builder.toJSON()); // one global command
await client.bulkEditCommands([builder.toJSON()]); // replace all global
await client.bulkEditGuildCommands(guildID, [builder.toJSON()]); // instant, per guildWith the framework, call deployCommands() once on ready and it picks guild or global scope from NODE_ENV and DEV_GUILD. See Commands framework.
Receiving a command without the framework
client.on('interactionCreate', async (interaction) => {
if (!interaction.isCommand() || interaction.data.name !== 'avatar') return;
const user = interaction.getUser('user') ?? interaction.user;
await interaction.createMessage({ embeds: [{ title: user.username, image: { url: user.dynamicAvatarURL('png', 1024) } }] });
});Option getter quick map
| Builder method | Receiver |
|---|---|
addStringOption | getString |
addIntegerOption | getInteger |
addNumberOption | getNumber |
addBooleanOption | getBoolean |
addUserOption | getUser / getMember |
addChannelOption | getChannel |
addRoleOption | getRole |
addMentionOption | getMentionable |
addAttachmentOption | getAttachment |