Slash commands are the modern way for users to interact with Discord bots. This example demonstrates how to create various types of slash commands with options, permissions, and button interactions.Documentation Index
Fetch the complete documentation index at: https://mintlify.com/discord-jda/JDA/llms.txt
Use this file to discover all available pages before exploring further.
Overview
This bot implements four slash commands:/ban- Ban a user with optional reason and message deletion/say- Make the bot repeat a message/leave- Make the bot leave the server/prune- Delete multiple messages with confirmation
Setup
Configure Gateway Intents
Slash commands don’t require any specific intents:
EnumSet<GatewayIntent> intents = EnumSet.noneOf(GatewayIntent.class);
JDA jda = JDABuilder.createLight("BOT_TOKEN_HERE", intents)
.addEventListeners(new SlashBotExample())
.build();
Register Your Commands
Use
updateCommands() to register slash commands with Discord:CommandListUpdateAction commands = jda.updateCommands();
commands.addCommands(
Commands.slash("say", "Makes the bot say what you tell it to")
.addOption(STRING, "content", "What the bot should say", true)
);
commands.queue();
You might need to reload your Discord client to see newly registered commands.
Handle Command Interactions
Override
onSlashCommandInteraction to handle commands:@Override
public void onSlashCommandInteraction(SlashCommandInteractionEvent event) {
switch (event.getName()) {
case "say":
String content = event.getOption("content").getAsString();
event.reply(content).queue();
break;
}
}
Creating Commands with Options
Required Options
Options can be required or optional:commands.addCommands(
Commands.slash("ban", "Ban a user from this server")
.addOptions(new OptionData(USER, "user", "The user to ban")
.setRequired(true)) // Must be provided
.addOptions(new OptionData(INTEGER, "del_days", "Delete messages from the past days")
.setRequiredRange(0, 7)) // Optional, but if provided must be 0-7
.addOptions(new OptionData(STRING, "reason", "The ban reason"))
);
Command Contexts and Integration Types
Control where commands can be used:// Available everywhere (Bot DMs, Guilds, Friend DMs, Group DMs)
Commands.slash("say", "Makes the bot say what you tell it to")
.setContexts(InteractionContextType.ALL)
.setIntegrationTypes(IntegrationType.ALL)
.addOption(STRING, "content", "What the bot should say", true);
// Only in guilds
Commands.slash("ban", "Ban a user from this server")
.setContexts(InteractionContextType.GUILD)
.setDefaultPermissions(DefaultMemberPermissions.enabledFor(Permission.BAN_MEMBERS));
Permission Requirements
Set default permissions for who can see and use commands:Commands.slash("leave", "Make the bot leave the server")
.setContexts(InteractionContextType.GUILD)
// Only admins can see this command
.setDefaultPermissions(DefaultMemberPermissions.DISABLED);
Commands.slash("prune", "Prune messages from this channel")
.addOption(INTEGER, "amount", "How many messages to prune (Default 100)")
.setContexts(InteractionContextType.GUILD)
// Only members with MESSAGE_MANAGE permission can see this
.setDefaultPermissions(DefaultMemberPermissions.enabledFor(Permission.MESSAGE_MANAGE));
Handling Command Interactions
Accessing Options
Retrieve option values from the event:@Override
public void onSlashCommandInteraction(SlashCommandInteractionEvent event) {
switch (event.getName()) {
case "ban":
// Required option - no null check needed
User user = event.getOption("user").getAsUser();
Member member = event.getOption("user").getAsMember();
// Optional with default value
int delDays = event.getOption("del_days", 0, OptionMapping::getAsInt);
// Optional with lazy fallback
String reason = event.getOption(
"reason",
() -> "Banned by " + event.getUser().getName(),
OptionMapping::getAsString
);
ban(event, user, member);
break;
}
}
Deferring Replies
For operations that take time, defer the reply:public void ban(SlashCommandInteractionEvent event, User user, Member member) {
// Acknowledge the command immediately
event.deferReply(true).queue(); // true = ephemeral
// Get the interaction hook for later responses
InteractionHook hook = event.getHook();
hook.setEphemeral(true); // All messages will be ephemeral
// Perform checks
if (!event.getMember().hasPermission(Permission.BAN_MEMBERS)) {
hook.sendMessage("You do not have permission to ban users.").queue();
return;
}
// Execute the ban
event.getGuild()
.ban(user, delDays, TimeUnit.DAYS)
.reason(reason)
.flatMap(v -> hook.sendMessage("Banned user " + user.getName()))
.queue();
}
Interactive Components with Buttons
Add buttons to your responses for confirmation dialogs:public void prune(SlashCommandInteractionEvent event) {
OptionMapping amountOption = event.getOption("amount");
int amount = amountOption == null ? 100 : (int) Math.min(200, Math.max(2, amountOption.getAsLong()));
String userId = event.getUser().getId();
// Prompt with buttons
event.reply("This will delete " + amount + " messages.\nAre you sure?")
.addComponents(ActionRow.of(
Button.secondary(userId + ":delete", "Nevermind!"),
Button.danger(userId + ":prune:" + amount, "Yes!")
))
.queue();
}
Handling Button Clicks
@Override
public void onButtonInteraction(ButtonInteractionEvent event) {
String[] id = event.getComponentId().split(":");
String authorId = id[0];
String type = id[1];
// Verify the user clicking is the one who ran the command
if (!authorId.equals(event.getUser().getId())) {
return;
}
// Acknowledge the button click
event.deferEdit().queue();
MessageChannel channel = event.getChannel();
switch (type) {
case "prune" -> {
int amount = Integer.parseInt(id[2]);
event.getChannel()
.getIterableHistory()
.skipTo(event.getMessageIdLong())
.takeAsync(amount)
.thenAccept(channel::purgeMessages);
// Delete the prompt message
event.getHook().deleteOriginal().queue();
}
case "delete" -> {
event.getHook().deleteOriginal().queue();
}
}
}
Complete Example
import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.JDABuilder;
import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.components.actionrow.ActionRow;
import net.dv8tion.jda.api.components.buttons.Button;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.User;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import net.dv8tion.jda.api.interactions.IntegrationType;
import net.dv8tion.jda.api.interactions.InteractionContextType;
import net.dv8tion.jda.api.interactions.InteractionHook;
import net.dv8tion.jda.api.interactions.commands.DefaultMemberPermissions;
import net.dv8tion.jda.api.interactions.commands.OptionMapping;
import net.dv8tion.jda.api.interactions.commands.build.Commands;
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
import net.dv8tion.jda.api.requests.GatewayIntent;
import net.dv8tion.jda.api.requests.restaction.CommandListUpdateAction;
import java.util.EnumSet;
import java.util.concurrent.TimeUnit;
import static net.dv8tion.jda.api.interactions.commands.OptionType.*;
public class SlashBotExample extends ListenerAdapter {
public static void main(String[] args) {
EnumSet<GatewayIntent> intents = EnumSet.noneOf(GatewayIntent.class);
JDA jda = JDABuilder.createLight("BOT_TOKEN_HERE", intents)
.addEventListeners(new SlashBotExample())
.build();
CommandListUpdateAction commands = jda.updateCommands();
commands.addCommands(
Commands.slash("ban", "Ban a user from this server. Requires permission to ban users.")
.addOptions(new OptionData(USER, "user", "The user to ban").setRequired(true))
.addOptions(new OptionData(INTEGER, "del_days", "Delete messages from the past days.").setRequiredRange(0, 7))
.addOptions(new OptionData(STRING, "reason", "The ban reason to use (default: Banned by <user>)"))
.setContexts(InteractionContextType.GUILD)
.setDefaultPermissions(DefaultMemberPermissions.enabledFor(Permission.BAN_MEMBERS))
);
commands.addCommands(
Commands.slash("say", "Makes the bot say what you tell it to")
.setContexts(InteractionContextType.ALL)
.setIntegrationTypes(IntegrationType.ALL)
.addOption(STRING, "content", "What the bot should say", true)
);
commands.addCommands(
Commands.slash("leave", "Make the bot leave the server")
.setContexts(InteractionContextType.GUILD)
.setDefaultPermissions(DefaultMemberPermissions.DISABLED)
);
commands.addCommands(
Commands.slash("prune", "Prune messages from this channel")
.addOption(INTEGER, "amount", "How many messages to prune (Default 100)")
.setContexts(InteractionContextType.GUILD)
.setDefaultPermissions(DefaultMemberPermissions.enabledFor(Permission.MESSAGE_MANAGE))
);
commands.queue();
}
@Override
public void onSlashCommandInteraction(SlashCommandInteractionEvent event) {
if (event.getGuild() == null) {
return;
}
switch (event.getName()) {
case "ban":
Member member = event.getOption("user").getAsMember();
User user = event.getOption("user").getAsUser();
ban(event, user, member);
break;
case "say":
say(event, event.getOption("content").getAsString());
break;
case "leave":
leave(event);
break;
case "prune":
prune(event);
break;
default:
event.reply("I can't handle that command right now :(")
.setEphemeral(true)
.queue();
}
}
@Override
public void onButtonInteraction(ButtonInteractionEvent event) {
String[] id = event.getComponentId().split(":");
String authorId = id[0];
String type = id[1];
if (!authorId.equals(event.getUser().getId())) {
return;
}
event.deferEdit().queue();
MessageChannel channel = event.getChannel();
switch (type) {
case "prune" -> {
int amount = Integer.parseInt(id[2]);
event.getChannel()
.getIterableHistory()
.skipTo(event.getMessageIdLong())
.takeAsync(amount)
.thenAccept(channel::purgeMessages);
event.getHook().deleteOriginal().queue();
}
case "delete" -> {
event.getHook().deleteOriginal().queue();
}
}
}
public void ban(SlashCommandInteractionEvent event, User user, Member member) {
event.deferReply(true).queue();
InteractionHook hook = event.getHook();
hook.setEphemeral(true);
if (!event.getMember().hasPermission(Permission.BAN_MEMBERS)) {
hook.sendMessage("You do not have the required permissions to ban users from this server.").queue();
return;
}
Member selfMember = event.getGuild().getSelfMember();
if (!selfMember.hasPermission(Permission.BAN_MEMBERS)) {
hook.sendMessage("I don't have the required permissions to ban users from this server.").queue();
return;
}
if (member != null && !selfMember.canInteract(member)) {
hook.sendMessage("This user is too powerful for me to ban.").queue();
return;
}
int delDays = event.getOption("del_days", 0, OptionMapping::getAsInt);
String reason = event.getOption("reason", () -> "Banned by " + event.getUser().getName(), OptionMapping::getAsString);
event.getGuild()
.ban(user, delDays, TimeUnit.DAYS)
.reason(reason)
.flatMap(v -> hook.sendMessage("Banned user " + user.getName()))
.queue();
}
public void say(SlashCommandInteractionEvent event, String content) {
event.reply(content).queue();
}
public void leave(SlashCommandInteractionEvent event) {
if (!event.getMember().hasPermission(Permission.KICK_MEMBERS)) {
event.reply("You do not have permissions to kick me.")
.setEphemeral(true)
.queue();
} else {
event.reply("Leaving the server... :wave:")
.flatMap(v -> event.getGuild().leave())
.queue();
}
}
public void prune(SlashCommandInteractionEvent event) {
OptionMapping amountOption = event.getOption("amount");
int amount = amountOption == null ? 100 : (int) Math.min(200, Math.max(2, amountOption.getAsLong()));
String userId = event.getUser().getId();
event.reply("This will delete " + amount + " messages.\nAre you sure?")
.addComponents(ActionRow.of(
Button.secondary(userId + ":delete", "Nevermind!"),
Button.danger(userId + ":prune:" + amount, "Yes!")
))
.queue();
}
}
Use ephemeral replies (
setEphemeral(true)) for messages that only the command user should see, like error messages or confirmations.