CommandKit provides a clean, organized way to handle Discord.js events using a file-based routing system. Each event handler file in your src/app/events/ directory automatically registers the corresponding Discord event.
Event file structure
Events are defined by default exporting a handler function from files in your events directory:
import type { EventHandler } from 'commandkit' ;
const handler : EventHandler < 'messageCreate' > = async ( message , client , commandkit ) => {
if ( message . author . bot ) return ;
console . log ( `Message from ${ message . author . tag } : ${ message . content } ` );
};
export default handler ;
Event handler pattern
The EventHandler type is generic and accepts the event name as a type parameter. This provides full type safety for event arguments:
import type { EventHandler } from 'commandkit' ;
// The handler automatically receives the correct argument types
const handler : EventHandler < 'clientReady' > = ( client ) => {
console . log ( `Logged in as ${ client . user ?. tag } ` );
};
export default handler ;
Generic type for event handlers that provides type-safe event arguments. Type signature: type EventHandler < K extends keyof ClientEvents > = (
... args : [ ... ClientEvents [ K ], Client < true >, CommandKit ]
) => void | Promise < void >;
The handler receives:
All event-specific arguments from Discord.js
The Discord.js Client instance
The CommandKit instance
Directory structure
Organize event handlers using directories that match Discord.js event names:
src/app/events/
├── clientReady/
│ └── logger.ts
├── messageCreate/
│ ├── say-hi.ts
│ └── give-xp.ts
├── guildMemberAdd/
│ └── welcome.ts
└── interactionCreate/
└── button-handler.ts
Each directory name should match a Discord.js event name. You can have multiple handlers for the same event by creating multiple files in the event directory.
Available events
CommandKit supports all Discord.js client events. Here are the most commonly used:
Common events
Guild events
Voice events
Other events
clientReady - Fired when the client is ready
messageCreate - Fired when a message is created
messageUpdate - Fired when a message is updated
messageDelete - Fired when a message is deleted
guildMemberAdd - Fired when a member joins a guild
guildMemberRemove - Fired when a member leaves a guild
interactionCreate - Fired when an interaction is created
guildCreate - Bot joins a guild
guildDelete - Bot leaves a guild
guildUpdate - Guild is updated
guildMemberUpdate - Member is updated
guildBanAdd - Member is banned
guildBanRemove - Member is unbanned
voiceStateUpdate - Voice state is updated
voiceConnectionUpdate - Voice connection is updated
channelCreate - Channel is created
channelDelete - Channel is deleted
channelUpdate - Channel is updated
roleCreate - Role is created
roleDelete - Role is deleted
roleUpdate - Role is updated
Real-world examples
Client ready event
Log when the bot successfully connects to Discord:
src/app/events/clientReady/logger.ts
import { EventHandler , Logger } from 'commandkit' ;
const handler : EventHandler < 'clientReady' > = ( client ) => {
Logger . log ( `Successfully logged in as ${ client . user ?. tag } ` );
};
export default handler ;
Message event with filter
Respond to specific messages:
src/app/events/messageCreate/say-hi.ts
import type { Message } from 'discord.js' ;
export default function ( message : Message ) {
if ( message . author . bot ) return ;
if ( message . content . toLowerCase () === 'hello' ) {
message . reply ( 'Hi there! 👋' );
}
}
XP system
Give users XP when they send messages:
src/app/events/messageCreate/give-xp.ts
import type { EventHandler } from 'commandkit' ;
import { database } from '@/database/store' ;
const handler : EventHandler < 'messageCreate' > = async ( message ) => {
if ( message . author . bot || ! message . inGuild ()) return ;
const key = `xp: ${ message . guildId } : ${ message . author . id } ` ;
const oldXp = database . get < number >( key ) ?? 0 ;
const xp = Math . floor ( Math . random () * 10 ) + 1 ;
const newXp = oldXp + xp ;
database . set ( key , newXp );
};
export default handler ;
Welcome message
Welcome new members to your server:
src/app/events/guildMemberAdd/welcome.ts
import type { EventHandler } from 'commandkit' ;
const handler : EventHandler < 'guildMemberAdd' > = async ( member ) => {
const channel = member . guild . systemChannel ;
if ( ! channel ) return ;
await channel . send ({
embeds: [{
title: 'Welcome!' ,
description: `Welcome to the server, ${ member } !` ,
color: 0x00ff00 ,
thumbnail: { url: member . user . displayAvatarURL () },
}],
});
};
export default handler ;
Member leave tracking
Log when members leave the server:
src/app/events/guildMemberRemove/log-leave.ts
import type { EventHandler } from 'commandkit' ;
import { Logger } from 'commandkit' ;
const handler : EventHandler < 'guildMemberRemove' > = async ( member ) => {
Logger . info ( ` ${ member . user . tag } left ${ member . guild . name } ` );
const logChannel = member . guild . channels . cache . get ( 'LOG_CHANNEL_ID' );
if ( logChannel ?. isTextBased ()) {
await logChannel . send ( `📤 ${ member . user . tag } left the server` );
}
};
export default handler ;
Voice state monitoring
Track when users join or leave voice channels:
src/app/events/voiceStateUpdate/voice-logger.ts
import type { EventHandler } from 'commandkit' ;
const handler : EventHandler < 'voiceStateUpdate' > = async ( oldState , newState ) => {
const member = newState . member ;
if ( ! member ) return ;
// User joined a voice channel
if ( ! oldState . channel && newState . channel ) {
console . log ( ` ${ member . user . tag } joined ${ newState . channel . name } ` );
}
// User left a voice channel
if ( oldState . channel && ! newState . channel ) {
console . log ( ` ${ member . user . tag } left ${ oldState . channel . name } ` );
}
// User switched voice channels
if ( oldState . channel && newState . channel && oldState . channel . id !== newState . channel . id ) {
console . log ( ` ${ member . user . tag } moved from ${ oldState . channel . name } to ${ newState . channel . name } ` );
}
};
export default handler ;
Message edit logging
Log message edits for moderation:
src/app/events/messageUpdate/log-edits.ts
import type { EventHandler } from 'commandkit' ;
const handler : EventHandler < 'messageUpdate' > = async ( oldMessage , newMessage ) => {
if ( ! newMessage . guild ) return ;
if ( oldMessage . content === newMessage . content ) return ;
const logChannel = newMessage . guild . channels . cache . get ( 'LOG_CHANNEL_ID' );
if ( logChannel ?. isTextBased ()) {
await logChannel . send ({
embeds: [{
title: 'Message Edited' ,
description: `Message by ${ newMessage . author } edited in ${ newMessage . channel } ` ,
fields: [
{ name: 'Before' , value: oldMessage . content || 'No content' , inline: false },
{ name: 'After' , value: newMessage . content || 'No content' , inline: false },
],
color: 0xffa500 ,
timestamp: new Date (). toISOString (),
}],
});
}
};
export default handler ;
Event namespacing
You can namespace events using directories with custom names:
src/app/events/
└── (extended)/
└── messageTyping/
└── index.ts
Extended or custom events can be registered through plugins or custom event emitters.
Multiple handlers for same event
Create multiple handlers for the same event by adding multiple files:
src/app/events/
└── messageCreate/
├── auto-mod.ts
├── xp-system.ts
├── command-logging.ts
└── anti-spam.ts
All handlers will execute when the event fires. They run in parallel by default.
Event handler context
Event handlers receive the full CommandKit context:
import type { EventHandler } from 'commandkit' ;
const handler : EventHandler < 'messageCreate' > = async ( message , client , commandkit ) => {
// message: Discord.js Message object
// client: Discord.js Client instance
// commandkit: CommandKit instance
// Access CommandKit features
const commands = commandkit . commandHandler . loadedCommands ;
// Access client
console . log ( `Bot user: ${ client . user ?. tag } ` );
};
export default handler ;
Best practices
Keep handlers focused
Each event handler should do one thing well. Split complex logic into multiple handlers.
Filter early
Return early if the event doesn’t match your criteria: if ( message . author . bot ) return ;
if ( ! message . inGuild ()) return ;
Handle errors
Always wrap async operations in try-catch blocks: try {
await message . reply ( 'Hello!' );
} catch ( error ) {
console . error ( 'Failed to reply:' , error );
}
Use TypeScript
Leverage the EventHandler type for full type safety: const handler : EventHandler < 'messageCreate' > = async ( message ) => {
// message is fully typed
};
Next steps
Middlewares Add middleware to your commands
Plugins Extend CommandKit with plugins