Skip to main content
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.

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

1

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();
2

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.
3

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.

Build docs developers (and LLMs) love