The essential discord.js v14 toolkit — prefix commands, slash commands, context menus, modals, select menus, embed presets, button paginator, event handler, and full Components V2 container support. One install, everything you need.
npm install devcodes-djsPeer requirement:
discord.js ^14.0.0
Container components requirediscord.js >=14.18.0
Organize all your client events in one clean, chainable place instead of scattered client.on() calls.
const { EventHandler } = require('devcodes-djs');
const events = new EventHandler()
.once('ready', (client) => console.log(`Logged in as ${client.user.tag}`))
.on('guildMemberAdd', (member) => {
member.guild.systemChannel?.send(`👋 Welcome ${member}!`);
})
.on('messageDelete', (message) => {
console.log(`Message deleted in #${message.channel.name}`);
});
events.attach(client); // binds all listeners at onceBuild and handle multi-field modal (popup form) interactions.
const { buildModal, ModalHandler, TextInputStyle } = require('devcodes-djs');
const feedbackModal = buildModal({
id: 'feedback_form',
title: 'Submit Feedback',
fields: [
{
id: 'name',
label: 'Your name',
style: TextInputStyle.Short,
placeholder: 'e.g. John',
required: true,
},
{
id: 'message',
label: 'Your feedback',
style: TextInputStyle.Paragraph,
minLength: 10,
maxLength: 1000,
},
],
});
// Show the modal from a slash command or button
slash.command({
name: 'feedback',
description: 'Submit feedback',
execute: async (interaction) => {
await interaction.showModal(feedbackModal);
},
});
// Handle the submission
const modals = new ModalHandler()
.on('feedback_form', async (interaction) => {
const name = interaction.fields.getTextInputValue('name');
const message = interaction.fields.getTextInputValue('message');
await interaction.reply({ content: `Thanks ${name}! Got: "${message}"`, ephemeral: true });
});
modals.attach(client);Use ID prefixes to handle modals with dynamic IDs:
modals.on('report_', async (interaction) => {
// matches "report_123", "report_456", etc.
const userId = interaction.customId.replace('report_', '');
await interaction.reply({ content: `Report filed for user ${userId}`, ephemeral: true });
});Build all 5 Discord select menu types and handle their interactions.
const { SelectMenus, SelectHandler } = require('devcodes-djs');
const colorMenu = SelectMenus.stringSelect({
id: 'color_pick',
placeholder: 'Choose a color',
options: [
{ label: 'Red', value: 'red', emoji: '🔴' },
{ label: 'Green', value: 'green', emoji: '🟢', default: true },
{ label: 'Blue', value: 'blue', emoji: '🔵' },
],
maxValues: 2,
});
await message.reply({ components: [colorMenu] });
// Handle selection
const selects = new SelectHandler()
.on('color_pick', async (interaction) => {
await interaction.reply(`You picked: **${interaction.values.join(', ')}**`);
});
selects.attach(client);// User picker
const picker = SelectMenus.userSelect({ id: 'pick_user', placeholder: 'Pick a user', maxValues: 3 });
// Role picker
const roles = SelectMenus.roleSelect({ id: 'pick_role', placeholder: 'Pick a role' });
// Channel picker (text channels only)
const chans = SelectMenus.channelSelect({ id: 'pick_chan', channelTypes: [ChannelType.GuildText] });
// Mentionable (users + roles)
const ment = SelectMenus.mentionableSelect({ id: 'pick_mention' });Add right-click commands to users and messages.
const { ContextMenuHandler } = require('devcodes-djs');
const ctx = new ContextMenuHandler({
clientId: process.env.CLIENT_ID,
token: process.env.BOT_TOKEN,
guildId: process.env.GUILD_ID,
});
ctx
.command({
type: 'user',
name: 'Get Avatar',
execute: async (interaction) => {
const url = interaction.targetUser.displayAvatarURL({ size: 512 });
await interaction.reply({ content: url, ephemeral: true });
},
})
.command({
type: 'user',
name: 'User Info',
execute: async (interaction) => {
const user = interaction.targetUser;
await interaction.reply({
embeds: [Embeds.info(`👤 ${user.tag}`, `ID: \`${user.id}\`\nCreated: <t:${Math.floor(user.createdTimestamp / 1000)}:R>`)],
ephemeral: true,
});
},
})
.command({
type: 'message',
name: 'Translate Message',
execute: async (interaction) => {
await interaction.reply({ content: `📝 Content: ${interaction.targetMessage.content}`, ephemeral: true });
},
});
ctx.attach(client);
client.once('ready', () => ctx.deploy());Register, deploy, and handle application (/) commands with full support for options, subcommands, permissions, and autocomplete.
const { Client, GatewayIntentBits } = require('discord.js');
const { SlashHandler, Embeds } = require('devcodes-djs');
const client = new Client({ intents: [GatewayIntentBits.Guilds] });
const slash = new SlashHandler({
clientId: process.env.CLIENT_ID,
token: process.env.BOT_TOKEN,
guildId: process.env.GUILD_ID, // omit for global commands
});
slash
.command({
name: 'ping',
description: 'Check bot latency',
execute: async (interaction) => {
await interaction.reply({
embeds: [Embeds.success('Pong!', `Latency: ${client.ws.ping}ms`)],
});
},
})
.command({
name: 'say',
description: 'Make the bot say something',
options: [
{ name: 'message', description: 'What to say', type: 'string', required: true },
{ name: 'ephemeral', description: 'Only visible to you', type: 'boolean' },
],
execute: async (interaction) => {
const text = interaction.options.getString('message', true);
const ephemeral = interaction.options.getBoolean('ephemeral') ?? false;
await interaction.reply({ content: text, ephemeral });
},
});
// Attach to client — handles interactionCreate automatically
slash.attach(client);
client.once('ready', async () => {
await slash.deploy(); // register commands with Discord
console.log('Ready!');
});
client.login(process.env.BOT_TOKEN);slash.command({
name: 'settings',
description: 'Manage bot settings',
subcommands: [
{
name: 'set',
description: 'Change a setting',
options: [
{ name: 'key', description: 'Setting name', type: 'string', required: true },
{ name: 'value', description: 'Setting value', type: 'string', required: true },
],
execute: async (interaction) => {
const key = interaction.options.getString('key', true);
const value = interaction.options.getString('value', true);
await interaction.reply({ embeds: [Embeds.success('Updated', `\`${key}\` → \`${value}\``)] });
},
},
{
name: 'reset',
description: 'Reset all settings to defaults',
execute: async (interaction) => {
await interaction.reply({ embeds: [Embeds.warn('Reset', 'All settings restored.')] });
},
},
],
});const fruits = ['apple', 'banana', 'cherry', 'durian', 'elderberry'];
slash.command({
name: 'fruit',
description: 'Pick a fruit',
options: [
{ name: 'name', description: 'Fruit name', type: 'string', required: true, autocomplete: true },
],
autocomplete: async (interaction) => {
const focused = interaction.options.getFocused().toLowerCase();
await interaction.respond(
fruits.filter(f => f.startsWith(focused)).map(f => ({ name: f, value: f }))
);
},
execute: async (interaction) => {
await interaction.reply(`You picked: **${interaction.options.getString('name', true)}**`);
},
});slash.command({
name: 'ban',
description: 'Ban a user',
permissions: ['BanMembers'],
options: [
{ name: 'user', description: 'User to ban', type: 'user', required: true },
],
execute: async (interaction) => {
const user = interaction.options.getUser('user', true);
await interaction.reply({ embeds: [Embeds.success('Banned', `${user.tag} was banned.`)] });
},
});| Type | discord.js getter |
|---|---|
string |
options.getString(name) |
integer |
options.getInteger(name) |
number |
options.getNumber(name) |
boolean |
options.getBoolean(name) |
user |
options.getUser(name) |
channel |
options.getChannel(name) |
role |
options.getRole(name) |
mentionable |
options.getMentionable(name) |
attachment |
options.getAttachment(name) |
| Option | Type | Description |
|---|---|---|
clientId |
string |
Your bot's application ID |
token |
string |
Bot token for REST deployment |
guildId |
string? |
Deploy to a specific guild (instant). Omit for global. |
Replace giant if/else chains with a clean, chainable command registry. Supports aliases, per-user cooldowns, and permission guards.
const { CommandHandler } = require('devcodes-djs');
const handler = new CommandHandler({ prefix: '!' });
handler
.command('ping', async (message) => {
await message.reply('Pong!');
})
.command('hello', async (message, args) => {
await message.reply(`Hey ${args[0] ?? 'there'}!`);
}, {
aliases: ['hi', 'hey'],
description: 'Say hello',
usage: '[name]',
})
.command('ban', async (message, args) => {
await message.reply(`Banned ${args[0]}`);
}, {
permissions: ['BanMembers'],
cooldown: 5000,
description: 'Ban a user',
usage: '<user>',
});
// In your messageCreate listener:
client.on('messageCreate', (message) => handler.handle(message));Use handler.getCommands() to build a dynamic help menu:
handler.command('help', async (message) => {
const lines = handler.getCommands()
.map(cmd => `**!${cmd.name}** ${cmd.options.usage ?? ''} — ${cmd.options.description ?? ''}`)
.join('\n');
await message.reply(lines);
});| Option | Type | Default | Description |
|---|---|---|---|
prefix |
string |
"!" |
Command prefix |
ignoreBots |
boolean |
true |
Ignore messages from bots |
| Option | Type | Default | Description |
|---|---|---|---|
aliases |
string[] |
[] |
Alternative trigger names |
cooldown |
number |
0 |
Per-user cooldown in ms |
permissions |
PermissionResolvable[] |
[] |
Required member permissions |
description |
string |
— | Short description |
usage |
string |
— | Usage hint e.g. <user> [reason] |
One-liner embeds with consistent colors — no more chaining .setColor().setTitle().setFooter() every time.
const { Embeds } = require('devcodes-djs');
// ✅ Green
await message.reply({ embeds: [Embeds.success('Done!', 'Operation completed.')] });
// ❌ Red
await message.reply({ embeds: [Embeds.error('Failed', 'Something went wrong.')] });
// ℹ️ Blurple
await message.reply({ embeds: [Embeds.info('Info', 'Here is some information.')] });
// ⚠️ Yellow
await message.reply({ embeds: [Embeds.warn('Warning', 'Be careful.')] });
// 🎨 Any color
await message.reply({ embeds: [Embeds.custom(0xFF8800, { title: 'Custom', description: 'Orange embed' })] });await message.reply({
embeds: [
Embeds.success('Deployment Complete', 'Version **1.4.2** is live.', {
fields: [
{ name: 'Environment', value: 'Production', inline: true },
{ name: 'Duration', value: '42s', inline: true },
],
footer: 'Dev Codes CI',
thumbnail: 'https://example.com/logo.png',
timestamp: true,
}),
],
});| Method | Color |
|---|---|
Embeds.success() |
#57F287 (green) |
Embeds.error() |
#ED4245 (red) |
Embeds.info() |
#5865F2 (blurple) |
Embeds.warn() |
#FEE75C (yellow) |
Embeds.custom(color, opts) |
Any hex integer |
Reply with a paginated embed controlled by ⏮ ◀ ✖ ▶ ⏭ buttons. Buttons disable automatically at the first/last page and are removed when the collector times out.
const { paginate, Embeds } = require('devcodes-djs');
handler.command('list', async (message) => {
const pages = [
Embeds.info('Page 1', 'First batch of results...'),
Embeds.info('Page 2', 'Second batch of results...'),
Embeds.info('Page 3', 'Third batch of results...'),
];
await paginate(message, pages, { timeout: 60_000, authorOnly: true });
});| Option | Type | Default | Description |
|---|---|---|---|
timeout |
number |
30000 |
Button collector timeout in ms |
authorOnly |
boolean |
true |
Only the command author can interact |
Build rich Discord Components V2 messages with a clean, fluent API. Containers support text, sections with thumbnails, separators, media galleries, and button rows — all composable.
⚠️ Components V2 messages require theMessageFlags.IsComponentsV2flag and cannot include embeds.
const { Containers } = require('devcodes-djs');
const { MessageFlags, ButtonBuilder, ButtonStyle } = require('discord.js');
const c = Containers.compose({
color: 0x57F287,
header: '✅ Deployment complete',
body: 'Version **1.4.2** is now live on production.',
thumbnail: 'https://example.com/logo.png',
images: ['https://example.com/screenshot.png'],
buttons: [
new ButtonBuilder().setCustomId('view').setLabel('View Logs').setStyle(ButtonStyle.Primary),
new ButtonBuilder().setCustomId('rollback').setLabel('Rollback').setStyle(ButtonStyle.Danger),
],
});
await message.reply({
components: [c],
flags: MessageFlags.IsComponentsV2,
});Use the individual factory functions for full control:
const {
container, text, section, separator, mediaGallery, buttonRow,
SeparatorSpacingSize,
} = require('devcodes-djs');
const { ButtonBuilder, ButtonStyle, MessageFlags } = require('discord.js');
const c = container(0x5865F2) // accent color
.addTextDisplayComponents(
text('**📦 New Release — v2.0.0**')
)
.addSeparatorComponents(
separator(true, SeparatorSpacingSize.Large)
)
.addSectionComponents(
section('A major update with breaking changes. Read the migration guide before upgrading.', 'https://example.com/icon.png')
)
.addTextDisplayComponents(
text('**Changes:**\n- Removed deprecated API\n- New `compose()` API\n- Performance improvements')
)
.addSeparatorComponents(separator())
.addMediaGalleryComponents(
mediaGallery([
{ url: 'https://example.com/before.png', description: 'Before' },
{ url: 'https://example.com/after.png', description: 'After' },
])
)
.addActionRowComponents(
buttonRow([
new ButtonBuilder().setCustomId('changelog').setLabel('Changelog').setStyle(ButtonStyle.Primary),
new ButtonBuilder().setURL('https://docs.example.com').setLabel('Docs').setStyle(ButtonStyle.Link),
])
);
await message.reply({ components: [c], flags: MessageFlags.IsComponentsV2 });| Function | What it creates |
|---|---|
container(color?, spoiler?) |
Top-level container, wraps everything |
text(content) |
Plain / markdown text block |
section(content, imageUrl?) |
Text + optional right-hand thumbnail |
separator(divider?, spacing?) |
Horizontal divider |
mediaGallery(items) |
Image gallery (up to 10 images) |
buttonRow(buttons[]) |
Action row of buttons |
compose(opts) |
Build a complete container from options |
| Option | Type | Description |
|---|---|---|
color |
number |
Accent color hex integer |
header |
string |
Bold header text |
body |
string |
Body text (shown in a section) |
thumbnail |
string |
Image URL next to body |
extra |
string[] |
Additional text blocks |
images |
string[] |
Media gallery image URLs |
imageSeparator |
boolean |
Separator before gallery (default: true) |
buttons |
ButtonBuilder[] |
Buttons row at the bottom |
spoiler |
boolean |
Mark container as spoiler |
const c = Containers.compose({
header: '🔞 Spoiler content',
body: 'This will be hidden behind a spoiler.',
spoiler: true,
});Full types for everything.
import {
CommandHandler,
Embeds,
paginate,
Containers,
type HandlerConfig,
type CommandOptions,
type EmbedPresetOptions,
type PaginatorOptions,
type QuickContainerOptions,
} from 'devcodes-djs';const { Client, GatewayIntentBits, MessageFlags, ButtonBuilder, ButtonStyle } = require('discord.js');
const { CommandHandler, Embeds, paginate, Containers } = require('devcodes-djs');
const client = new Client({ intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages, GatewayIntentBits.MessageContent] });
const handler = new CommandHandler({ prefix: '!' });
handler
.command('ping', async (msg) => {
await msg.reply({ embeds: [Embeds.success('Pong!', `Latency: ${client.ws.ping}ms`)] });
})
.command('list', async (msg) => {
await paginate(msg, [
Embeds.info('Results — Page 1', 'Item A\nItem B\nItem C'),
Embeds.info('Results — Page 2', 'Item D\nItem E\nItem F'),
]);
})
.command('card', async (msg) => {
const c = Containers.compose({
color: 0x5865F2,
header: '🤖 Bot Info',
body: `Servers: **${client.guilds.cache.size}**\nPing: **${client.ws.ping}ms**`,
thumbnail: client.user.displayAvatarURL(),
});
await msg.reply({ components: [c], flags: MessageFlags.IsComponentsV2 });
});
client.on('messageCreate', (msg) => handler.handle(msg));
client.login(process.env.BOT_TOKEN);MIT © azaresw
Join our Discord for help and updates: discord.gg/ESh2Dp2xX9