An awesome Discord Bot Handler with advanced features to jumpstart your projects!
Explore the docs »
View Examples
·
Report Bug
·
Request Feature
Table of Contents
This Discord Bot Handler is built to simplify the creation of powerful bots while maintaining a clean and organized codebase. With built-in support for modern Discord features like slash commands, buttons, and modals, it eliminates the hassle of handling boilerplate code. Whether you're crafting a small bot for personal use or a large-scale application, this handler lets you focus on adding the features that matter most.
This handler includes everything you need to create a powerful Discord Bot:
-
Command Handling:
- Support for Slash Commands, Context Menus, and Autocomplete.
- Classic Prefix Commands for users who prefer traditional interaction.
- Advanced argument features, including cooldowns, usage restrictions, and more.
-
Component Handling:
- Seamless integration with Discord Buttons, Select Menus, and Modals for interactive component elements.
-
Event Handling:
- Automatically manages event listeners.
-
Automatic Intents:
- No need to manually specify Gateway Intents—the handler determines and applies them automatically.
-
Reloadable Commands, Events, and Components:
- Commands, Events, and Components can be dynamically reloaded or added during runtime without the need for a bot restart.
-
Embed Pagination:
- Easy-to-use utilities for creating paginated embeds, perfect for displaying large datasets or navigation.
-
Colored Message Builder:
- A customizable message builder to create visually appealing messages.
- Simplicity: Abstracts away the repetitive tasks involved in bot development.
- Flexibility: Designed to support various bot types, from utility-focused to entertainment-driven.
- Scalability: Modular architecture makes it easy to extend and maintain as your bot grows.
- Community-Driven: Contributions and feedback are welcome to help make this the ultimate bot handler.
Follow these steps to install and set up the Discord Bot Handler.
This project is optimized for use with Bun, a fast JavaScript runtime. While you can use npm, we highly recommend using Bun for better performance. Starting the bot with Bun can be up to 5 times faster compared to using npm.
Before you begin, ensure you have the following:
- Node.js
- A Discord Bot Application (see Discord's developer portal to create a bot)
- Ensure the Discord Bot is configured with the
MESSAGE CONTENT
Privileged Gateway Intent enabled.- You can enable this in the Bot section under Settings on the Discord's developer portal.
- Note: If you prefer not to use this intent, you can disable it in the project's configuration file.
- Make sure the bot invite link includes the
applications.commands
scope to enable slash commands. Update the link in the OAuth2 section on the Discord's developer portal if needed.
To install Bun, run the following command in your terminal:
# Windows:
powershell -c "irm bun.sh/install.ps1 | iex"
# Linux:
curl -fsSL https://bun.sh/install | bash
If you prefer to use npm or something else, ensure it’s up-to-date:
- npm
npm install npm@latest -g
-
Clone the repository
git clone https://github.com/lukazbaum/discord-bot-handler
-
Install dependencies
bun install # or npm install
-
Configure environment variables
Rename
.env.example
to.env
in the project root and fill in your bot's details:CLIENT_TOKEN=your_discord_bot_token CLIENT_ID=your_discord_client_id GUILD_ID=your_discord_guild_id
Additionally, set your Discord ID (
ownerId
) in the config file to ensure that owner-specific commands work correctly. -
Run the bot
bun start # or npm run start:node
-
Build the bot (optional)
To build the bot for production:
bun build # or npm run build
Explore the documentation for in-depth insights on using and optimizing the Discord Bot Handler in your projects.
The following table explains the optional configuration arguments available for commands in your bot. Examples of how to use these arguments can be found within the bot's codebase.
Argument | Type | Description |
---|---|---|
userCooldown? |
number |
Cooldown (in seconds) for each user. |
guildCooldown? |
number |
Cooldown (in seconds) for all users in a guild. |
globalCooldown? |
number |
Cooldown (in seconds) for all users globally. |
allowedUsers? |
string[] |
List of user IDs explicitly allowed to use the command. |
blockedUsers? |
string[] |
List of user IDs explicitly blocked from using the command. |
optionalAllowedUsers? |
string[] |
List of optional user IDs where the command is conditionally allowed.¹ |
allowedChannels? |
string[] |
List of channel IDs where the command is allowed. |
blockedChannels? |
string[] |
List of channel IDs where the command is blocked. |
optionalAllowedChannels? |
string[] |
List of optional channel IDs where the command is conditionally allowed.¹ |
allowedCategories? |
string[] |
List of category IDs where the command is allowed. |
blockedCategories? |
string[] |
List of category IDs where the command is blocked. |
optionalAllowedCategories? |
string[] |
List of optional category IDs where the command is conditionally allowed.¹ |
allowedGuilds? |
string[] |
List of guild IDs where the command is allowed. |
blockedGuilds? |
string[] |
List of guild IDs where the command is blocked. |
optionalAllowedGuilds? |
string[] |
List of optional guild IDs where the command is conditionally allowed.¹ |
allowedRoles? |
string[] |
List of role IDs that can use the command. |
blockedRoles? |
string[] |
List of role IDs that are blocked from using the command. |
optionalAllowedRoles? |
string[] |
List of optional role IDs where the command is conditionally allowed.¹ |
restrictedToOwner? |
boolean |
Restricts the command to the bot owner only. |
restrictedToNSFW? |
boolean |
Restricts the command to NSFW channels only. |
isDisabled? |
boolean |
Disables the command entirely, making it unavailable for use. |
logUsage? |
boolean |
Logs the usage of a command to the channel specified in the config. |
aliases? |
string[] |
List of alternate names for the command that can be used to invoke it. (Only for PrefixCommands) |
¹Note on Optional Whitelists: When using optional whitelists, the command will be allowed to execute if any one of the optional whitelist conditions is met.
This bot supports several types of commands, including Slash Commands, Context Menus, Prefix Commands, and Autocomplete Commands.
Slash Commands
You can find more examples at: commands/slashexport default new SlashCommand({
registerType: RegisterType.Guild,
data: new SlashCommandBuilder()
.setName('ping')
.setDescription('Replies with pong!'),
async execute(interaction: ChatInputCommandInteraction): Promise<void> {
await interaction.reply({ content: "Pong!" });
},
});
registerType: RegisterType
Specifies where the command should be registered:.Guild
: Registers the command for a specific server (guild)..Global
: Registers the command for all servers where the bot is present.
Context Menus
You can find more examples at: commands/contextexport default new ContextMenu({
registerType: RegisterType.Guild,
data: new ContextMenuCommandBuilder()
.setName('Get Message ID')
.setType(ApplicationCommandType.Message as ContextMenuCommandType),
async execute(interaction: ContextMenuCommandInteraction): Promise<void> {
await interaction.reply({ content: `Message ID: ${interaction.targetId}`, ephemeral: true });
},
});
registerType: RegisterType
Specifies where the command should be registered:.Guild
: Registers the command for a specific server (guild)..Global
: Registers the command for all servers where the bot is present.
Prefix Commands
You can find more examples at: commands/prefixexport default new PrefixCommand({
name: 'ping',
aliases: ['peng'],
userCooldown: 10,
async execute(message: Message): Promise<any> {
await message.reply('Pong!');
},
});
Autocomplete Commands
You can find this example at: commands/slash/showcase/autocomplete.tsexport default new SlashCommand({
registerType: RegisterType.Guild,
data: new SlashCommandBuilder()
.setName('autocomplete')
.setDescription('Explore the autocomplete feature!')
.addStringOption((option) =>
option
.setName('topic')
.setDescription('Choose a topic from the suggestions')
.setAutocomplete(true)
.setRequired(true),
),
async autocomplete(interaction: AutocompleteInteraction): Promise<void> {
const focusedValue: string = interaction.options.getFocused().toLowerCase();
const choices: string[] = [
'Getting Started with Discord.js',
'Building Slash Commands',
'Understanding Permissions',
'Working with Autocomplete',
'Creating Buttons and Select Menus',
'Error Handling in Discord Bots',
];
const filtered: string[] = choices.filter((choice) => choice.toLowerCase().includes(focusedValue));
await interaction.respond(filtered.map((choice) => ({ name: choice, value: choice })));
},
async execute(interaction: ChatInputCommandInteraction): Promise<void> {
const selectedTopic: string = interaction.options.getString('topic', true);
await interaction.reply({
content: `You selected: **${selectedTopic}**`,
});
},
});
registerType: RegisterType
Specifies where the command should be registered:.Guild
: Registers the command for a specific server (guild)..Global
: Registers the command for all servers where the bot is present.
This bot supports all types of components, including Buttons, Select Menus, and Modals.
Property | Type | Description |
---|---|---|
customId |
string |
A unique identifier for the component. |
disabled? |
boolean |
Indicates whether the component is disabled. |
Unique IDs allow for more dynamic interaction handling by appending specific identifiers to the customId
of a component. These IDs are separated from the base identifier by a colon (:
).
.setCustomId('buttons:confirm')
async execute(interaction: ButtonInteraction, uniqueId: string | null): Promise<void> {
await interaction.reply({
content: `You have pressed the **${uniqueId}** button.`,
ephemeral: true,
});
}
In this example
buttons
is the base identifier (customId
) used to recognize the component.confirm
is the unique identifier (uniqueId
) that provides additional context or differentiation for the interaction.
If no unique ID is required, you can use just the base identifier, such as buttons
.
Buttons
You can find this example at: components/buttons/buttons.tsconst row = new ActionRowBuilder<ButtonBuilder>().addComponents(
new ButtonBuilder()
.setCustomId('buttons:confirm')
.setLabel('Confirm')
.setStyle(ButtonStyle.Success),
new ButtonBuilder()
.setCustomId('buttons:cancel')
.setLabel('Cancel')
.setStyle(ButtonStyle.Secondary),
new ButtonBuilder()
.setCustomId('buttons:info')
.setLabel('More Info')
.setStyle(ButtonStyle.Primary),
);
export default new Button({
customId: 'buttons',
async execute(interaction: ButtonInteraction, uniqueId: string | null): Promise<void> {
await interaction.reply({
content: `You have pressed the **${uniqueId}** button.`,
ephemeral: true,
});
},
});
Select Menus
You can find this example at: components/selectMenus/select.tsconst menu: StringSelectMenuBuilder = new StringSelectMenuBuilder()
.setCustomId('selectMenu')
.setPlaceholder('Choose wisely...')
.setMinValues(1)
.setMaxValues(1)
.addOptions(
{
label: 'Cats',
description: 'Choose this if you like cats',
value: 'cats',
emoji: '🐱',
},
{
label: 'Dogs',
description: 'Choose this if you like dogs',
value: 'dogs',
emoji: '🐶',
},
{
label: 'Birds',
description: 'Choose this if you like birds',
value: 'birds',
emoji: '🐦',
},
);
export default new SelectMenu({
customId: 'selectMenu',
async execute(interaction: AnySelectMenuInteraction, values: string[], uniqueIds: (string | null)[]): Promise<void> {
const choice: string = values[0];
const responses: Record<string, string> = {
cats: 'You chose cats! 🐱',
dogs: 'You chose dogs! 🐶',
birds: 'You chose birds! 🐦',
};
await interaction.reply({ content: responses[choice] });
},
});
Modals
You can find this example at: components/modals/askModal.tsconst modal: ModalBuilder = new ModalBuilder()
.setCustomId('askModal')
.setTitle('Tell us about yourself!');
const colorInput: TextInputBuilder = new TextInputBuilder()
.setCustomId('favoriteColor')
.setLabel("What's your favorite color?")
.setPlaceholder('e.g., Blue')
.setStyle(TextInputStyle.Short);
const hobbiesInput: TextInputBuilder = new TextInputBuilder()
.setCustomId('hobbies')
.setLabel("What's one of your favorite hobbies?")
.setPlaceholder('e.g., Reading books')
.setStyle(TextInputStyle.Paragraph);
const colorRow = new ActionRowBuilder<TextInputBuilder>().addComponents(colorInput);
const hobbiesRow = new ActionRowBuilder<TextInputBuilder>().addComponents(hobbiesInput);
modal.addComponents(colorRow, hobbiesRow);
export default new Modal({
customId: 'askModal',
async execute(interaction: ModalSubmitInteraction, fields: ModalSubmitFields): Promise<void> {
const favoriteColor: string = fields.getTextInputValue('favoriteColor');
const hobbies: string = fields.getTextInputValue('hobbies');
await interaction.reply({
content: `Your favorite color is **${favoriteColor}** and you enjoy **${hobbies}**!`,
ephemeral: true,
});
},
});
The EmbedPaginator
is a powerful utility for displaying paginated embeds with interactive buttons for navigating between pages. This is particularly useful for large sets of data or information, allowing users to browse through pages of content easily.
Field | Type | Description |
---|---|---|
pages |
(EmbedBuilder or PaginatorPage)[] |
An array of EmbedBuilder and PaginatorPage instances representing the pages of content to paginate. |
timeout |
number |
The time (in seconds) before the paginator buttons become inactive. |
buttons? |
ButtonPartial[] |
Custom settings for paginator buttons. Defines button type, label, style, and emoji. |
showButtonsAfterTimeout? |
boolean |
Whether to keep the paginator buttons visible after the timeout period. |
hideFirstLastButtons? |
boolean |
Whether to hide the "First" and "Last" buttons in the paginator. |
loopPages? |
boolean |
Whether to loop back to the first page after the last page is reached. |
autoPageDisplay? |
boolean |
Whether to display the current page number in the footer of the embed. |
restrictToAuthor? |
boolean |
Restricts interactions to the user who triggered the paginator. Default is true . |
PaginatorPage:
embed
: The embed displayed for this page. (EmbedBuilder
).components?
: Components to include on this page. Supports all validActionRowBuilder
types. (ActionRowBuilders[]
)
ButtonPartial:
type
: Specifies the type of the button (PaginatorButtonType
).label?
: The label of the button.style?
: The style of the button (ButtonStyle
).emoji?
: The emoji to display on the button.
Field | Type | Description |
---|---|---|
context |
Interaction or Message |
The interaction or message instance from the command. |
ephemeral? |
boolean |
Whether the paginator message should be ephemeral (only visible to the user who triggered the interaction). |
followUp? |
boolean |
Whether to send the paginator as a follow-up message (useful if replying to an initial interaction). |
content? |
string |
The content to display alongside the embed in the paginator message. |
const pages = [
new EmbedBuilder()
.setTitle('Welcome to the Paginator')
.setDescription('This is **Page 1** of the paginator.')
.setColor(Colors.Blue),
{
embed: new EmbedBuilder()
.setTitle('Page 2 with Buttons')
.setDescription('Here is **Page 2** with custom buttons.')
.setColor(Colors.Green),
components: [
new ActionRowBuilder<ButtonBuilder>().addComponents(
new ButtonBuilder()
.setCustomId('buttons:confirm')
.setLabel('Confirm')
.setStyle(ButtonStyle.Success),
new ButtonBuilder()
.setCustomId('buttons:cancel')
.setLabel('Cancel')
.setStyle(ButtonStyle.Danger),
),
],
},
new EmbedBuilder()
.setTitle('Page 3')
.setDescription('Finally, this is **Page 3**. Enjoy!')
.setColor(Colors.Red),
];
const paginator: EmbedPaginator = new EmbedPaginator({
pages,
timeout: 60,
autoPageDisplay: true,
});
await paginator.send({ context: interaction });
The ColoredMessageBuilder
is designed to enhance the visual appeal of your Discord bot's messages. It allows for various text formatting options, including text color, background color, and styling.
const coloredMessage: string = new ColoredMessageBuilder()
.add('Welcome to the ', Color.Green)
.add('Color Showcase!', Color.Blue, BackgroundColor.Orange, Format.Bold)
.addNewLine()
.add('This text is ', Color.Red)
.add('red ', Color.Red, BackgroundColor.None, Format.Bold)
.add('with an underline.', Color.Gray, BackgroundColor.None, Format.Underline)
.addNewLine()
.add('Let’s ', Color.White)
.add('explore ', Color.Cyan, BackgroundColor.None, Format.Bold)
.add('a ', Color.Yellow)
.addRainbow('rainbow ')
.add('effect: ', Color.Pink)
.addRainbow('rainboooooooow!', Format.Normal)
.addNewLine()
.add('Thanks for using the command!', Color.Cyan, BackgroundColor.MarbleBlue, Format.Bold)
.build();
await interaction.reply({ content: coloredMessage });
- Enhance the Embed Paginator to support non-interaction-based commands.
- Add a command argument which logs command usage.
- Enhance the Embed Paginator to support custom components.
- Increase customization options for denied command responses.
- Add command-line tools to quickly create commands, components and events with predefined templates.
- Implement a plugin system, enabling features like a ticket system to be seamlessly integrated into the project with a single line of code.
Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are greatly appreciated.
If you have a suggestion that would make this better, please fork the repo and create a pull request. You can also simply open an issue with the tag "enhancement". Don't forget to give the project a star! Thanks again!
- Fork the Project
- Create your Feature Branch (
git checkout -b feature/AmazingFeature
) - Commit your Changes (
git commit -m 'Add some AmazingFeature'
) - Push to the Branch (
git push origin feature/AmazingFeature
) - Open a Pull Request
Distributed under the MIT License. See LICENSE for more information.