Skip to main content
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;
EventHandler<K>
Type
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:
  • 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
For a complete list of Discord.js events, refer to the Discord.js documentation.

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

1

Keep handlers focused

Each event handler should do one thing well. Split complex logic into multiple handlers.
2

Filter early

Return early if the event doesn’t match your criteria:
if (message.author.bot) return;
if (!message.inGuild()) return;
3

Handle errors

Always wrap async operations in try-catch blocks:
try {
  await message.reply('Hello!');
} catch (error) {
  console.error('Failed to reply:', error);
}
4

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

Build docs developers (and LLMs) love