A discord.js v14 wrapper that makes bot development faster and easier β file-based auto-loading, typed base classes for commands, events, and components, plus simplified builder helpers.
npm install @fmfl-devteam.de/easy-discord.js discord.js
discord.jsis a peer dependency β you must install it alongside this package.
- Quick Start
- EasyClient
- Slash Commands
- Context Menu Commands
- Events
- Component Handlers
- Builders
- Logger
- File Structure
import { EasyClient } from '@fmfl-devteam/easy-discord.js';
import { GatewayIntentBits } from 'discord.js';
import path from 'path';
const client = new EasyClient({
token: process.env.TOKEN!,
clientId: process.env.CLIENT_ID!,
clientOptions: {
intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages],
},
commandsPath: path.join(__dirname, 'commands'),
eventsPath: path.join(__dirname, 'events'),
componentsPath: path.join(__dirname, 'components'),
// guildId: 'YOUR_GUILD_ID', // Optional: scope commands to a guild (instant, good for dev)
});
await client.start();client.start() will:
- Load and register all slash/context-menu commands with Discord
- Attach all event listeners
- Load all component handlers
- Log in the bot
new EasyClient(options: EasyClientOptions)| Option | Type | Required | Description |
|---|---|---|---|
token |
string |
β | Your bot token |
clientId |
string |
β | Your application's client ID |
clientOptions |
ClientOptions |
β | Standard discord.js client options (intents, etc.) |
commandsPath |
string |
β | Absolute path to commands directory |
eventsPath |
string |
β | Absolute path to events directory |
componentsPath |
string |
β | Absolute path to components directory |
guildId |
string |
β | Register commands to a specific guild (omit for global) |
Properties:
client.commandManagerβ access loaded commandsclient.eventManagerβ access registered eventsclient.componentManagerβ access component handlers
Create a file in your commands/ folder. The default export must be an instance of SlashCommand.
// commands/ping.ts
import { SlashCommand } from '@fmfl-devteam/easy-discord.js';
import { SlashCommandBuilder, ChatInputCommandInteraction } from 'discord.js';
export default new class PingCommand extends SlashCommand {
data = new SlashCommandBuilder()
.setName('ping')
.setDescription('Replies with Pong!');
async execute(interaction: ChatInputCommandInteraction) {
await interaction.reply('Pong! π');
}
};With autocomplete:
import { SlashCommand } from '@fmfl-devteam/easy-discord.js';
import {
SlashCommandBuilder,
ChatInputCommandInteraction,
AutocompleteInteraction,
} from 'discord.js';
export default new class SearchCommand extends SlashCommand {
data = new SlashCommandBuilder()
.setName('search')
.setDescription('Search something')
.addStringOption(o => o.setName('query').setDescription('Search query').setAutocomplete(true));
async execute(interaction: ChatInputCommandInteraction) {
const query = interaction.options.getString('query', true);
await interaction.reply(`Searching for: ${query}`);
}
async autocomplete(interaction: AutocompleteInteraction) {
const focused = interaction.options.getFocused();
const choices = ['apple', 'banana', 'cherry'].filter(c => c.startsWith(focused));
await interaction.respond(choices.map(c => ({ name: c, value: c })));
}
};// commands/avatar.ts
import { ContextMenuCommand } from '@fmfl-devteam/easy-discord.js';
import {
ContextMenuCommandBuilder,
ApplicationCommandType,
UserContextMenuCommandInteraction,
} from 'discord.js';
export default new class AvatarCommand extends ContextMenuCommand {
data = new ContextMenuCommandBuilder()
.setName('Get Avatar')
.setType(ApplicationCommandType.User);
async execute(interaction: UserContextMenuCommandInteraction) {
await interaction.reply(interaction.targetUser.displayAvatarURL({ size: 512 }));
}
};Create a file in your events/ folder. The default export must be an instance of Event.
// events/ready.ts
import { Event } from '@fmfl-devteam/easy-discord.js';
import { Client } from 'discord.js';
export default new class ReadyEvent extends Event<'ready'> {
name = 'ready' as const;
once = true;
async execute(client: Client<true>) {
console.log(`Logged in as ${client.user.tag}`);
}
};// events/guildMemberAdd.ts
import { Event } from '@fmfl-devteam/easy-discord.js';
import { GuildMember } from 'discord.js';
export default new class GuildMemberAddEvent extends Event<'guildMemberAdd'> {
name = 'guildMemberAdd' as const;
async execute(member: GuildMember) {
console.log(`${member.user.tag} joined ${member.guild.name}`);
}
};// components/confirm.ts
import { ButtonHandler } from '@fmfl-devteam/easy-discord.js';
import { ButtonInteraction } from 'discord.js';
export default new class ConfirmButton extends ButtonHandler {
customId = 'confirm';
async execute(interaction: ButtonInteraction) {
await interaction.reply({ content: 'β
Confirmed!', ephemeral: true });
}
};// components/role-select.ts
import { SelectMenuHandler } from '@fmfl-devteam/easy-discord.js';
import { AnySelectMenuInteraction } from 'discord.js';
export default new class RoleSelectMenu extends SelectMenuHandler {
customId = 'role-select';
async execute(interaction: AnySelectMenuInteraction) {
await interaction.reply({ content: `You picked: ${interaction.values.join(', ')}`, ephemeral: true });
}
};// components/feedback-modal.ts
import { ModalHandler } from '@fmfl-devteam/easy-discord.js';
import { ModalSubmitInteraction } from 'discord.js';
export default new class FeedbackModal extends ModalHandler {
customId = 'feedback-modal';
async execute(interaction: ModalSubmitInteraction) {
const feedback = interaction.fields.getTextInputValue('feedback');
await interaction.reply({ content: `Thanks for your feedback: ${feedback}`, ephemeral: true });
}
};Extends EmbedBuilder with color presets and convenience methods.
import { EasyEmbed } from '@fmfl-devteam/easy-discord.js';
// Color presets
new EasyEmbed().success().setTitle('Done!').setDescription('It worked!');
new EasyEmbed().error().setTitle('Error').setDescription('Something went wrong.');
new EasyEmbed().warning().setTitle('Warning').setDescription('Proceed with caution.');
new EasyEmbed().info().setTitle('Info').setDescription('Here is some info.');
// Helpers
new EasyEmbed()
.info()
.setTitle('Hello')
.setTimestampNow()
.setDefaultFooter('My Bot', 'https://example.com/icon.png');| Method | Color |
|---|---|
.success() |
Green #57F287 |
.error() |
Red #ED4245 |
.warning() |
Yellow #FEE75C |
.info() |
Blurple #5865F2 |
import { EasyButton } from '@fmfl-devteam/easy-discord.js';
import { ButtonStyle } from 'discord.js';
// Interaction button
new EasyButton({ label: 'Confirm', customId: 'confirm', style: ButtonStyle.Success });
new EasyButton({ label: 'Cancel', customId: 'cancel', style: ButtonStyle.Danger });
new EasyButton({ label: 'Click', customId: 'click', emoji: 'π' });
// Link button
EasyButton.link({ label: 'Visit Site', url: 'https://example.com' });import { EasySelectMenu } from '@fmfl-devteam/easy-discord.js';
const menu = new EasySelectMenu('color-select', 'Pick a color')
.addOption('Red', 'red', 'The color red', 'π΄')
.addOption('Blue', 'blue', 'The color blue', 'π΅')
.addOption('Green', 'green', 'The color green', 'π’')
.setMinValues(1)
.setMaxValues(1);import { EasyModal } from '@fmfl-devteam/easy-discord.js';
import { TextInputStyle } from 'discord.js';
const modal = new EasyModal('feedback-modal', 'Submit Feedback')
.addInput('name', 'Your name', TextInputStyle.Short, 'e.g. John')
.addInput('feedback', 'Your feedback', TextInputStyle.Paragraph, 'Tell us what you think...', true, 10, 500);import { EasyActionRow, EasyButton } from '@fmfl-devteam/easy-discord.js';
import { ButtonStyle } from 'discord.js';
const row = new EasyActionRow().addComponents(
new EasyButton({ label: 'Yes', customId: 'yes', style: ButtonStyle.Success }),
new EasyButton({ label: 'No', customId: 'no', style: ButtonStyle.Danger }),
);A built-in ANSI color-coded logger, prefixed with [easy-discord.js].
import { Logger } from '@fmfl-devteam/easy-discord.js';
Logger.info('Bot is starting...');
Logger.success('Commands registered!');
Logger.warn('Missing optional config.');
Logger.error('Something broke!', new Error('details'));
Logger.debug('Variable value: foo'); // only prints when process.env.DEBUG is setsrc/
index.ts β bot entry point (new EasyClient(...).start())
commands/
ping.ts
avatar.ts
events/
ready.ts
guildMemberAdd.ts
components/
confirm.ts β ButtonHandler
role-select.ts β SelectMenuHandler
feedback-modal.ts β ModalHandler