Skip to main content
CommandKit’s plugin system allows you to extend the framework with additional functionality. Plugins can hook into various lifecycle events, transform code at compile time, and add new features to your bot.

Plugin types

CommandKit supports two types of plugins:
Runtime plugins execute during bot runtime and can hook into lifecycle events:
import { RuntimePlugin } from 'commandkit';

class MyPlugin extends RuntimePlugin {
  async onBeforeCommandsLoad(ctx) {
    console.log('Commands are about to load');
  }
  
  async onAfterCommandsLoad(ctx) {
    console.log('Commands have been loaded');
  }
}

Installing plugins

Add plugins to your commandkit.config.ts file:
commandkit.config.ts
import { defineConfig } from 'commandkit/config';
import { i18n } from '@commandkit/i18n';
import { cache } from '@commandkit/cache';
import { devtools } from '@commandkit/devtools';

export default defineConfig({
  plugins: [
    i18n(),
    cache(),
    devtools(),
  ],
});
Plugins are initialized in the order they appear in the array.

Official plugins

CommandKit provides several official plugins:

i18n Plugin

Add internationalization support to your bot:
npm install @commandkit/i18n
commandkit.config.ts
import { i18n } from '@commandkit/i18n';

export default defineConfig({
  plugins: [
    i18n({
      locales: ['en', 'es', 'fr'],
      defaultLocale: 'en',
    }),
  ],
});
Use in commands:
export const chatInput: ChatInputCommand = async (ctx) => {
  const { t } = ctx.locale();
  await ctx.interaction.reply(t('welcome', { user: ctx.interaction.user.username }));
};

Cache Plugin

Add caching capabilities to your bot:
npm install @commandkit/cache
commandkit.config.ts
import { cache } from '@commandkit/cache';

export default defineConfig({
  plugins: [
    cache({
      ttl: 300, // 5 minutes
    }),
  ],
});
Use in commands:
import { getCached, setCached } from '@commandkit/cache';

export const chatInput: ChatInputCommand = async (ctx) => {
  const cached = await getCached('user-data');
  if (cached) {
    return ctx.interaction.reply(cached);
  }
  
  const data = await fetchUserData();
  await setCached('user-data', data, { ttl: 300 });
  await ctx.interaction.reply(data);
};

DevTools Plugin

Debug and monitor your bot in development:
npm install @commandkit/devtools
commandkit.config.ts
import { devtools } from '@commandkit/devtools';

export default defineConfig({
  plugins: [
    devtools({
      port: 3000,
    }),
  ],
});
Access the DevTools UI at http://localhost:3000

AI Plugin

Integrate AI capabilities into your bot:
npm install @commandkit/ai
commandkit.config.ts
import { ai } from '@commandkit/ai';

export default defineConfig({
  plugins: [
    ai({
      provider: 'openai',
      apiKey: process.env.OPENAI_API_KEY,
    }),
  ],
});

Tasks Plugin

Schedule and manage background tasks:
npm install @commandkit/tasks
commandkit.config.ts
import { tasks } from '@commandkit/tasks';
import { SQLiteDriver } from '@commandkit/tasks/sqlite';

export default defineConfig({
  plugins: [
    tasks({
      driver: new SQLiteDriver(),
    }),
  ],
});
Create tasks:
src/app/tasks/cleanup.ts
export default async function () {
  console.log('Running cleanup task...');
  // Your cleanup logic
}

export const schedule = '0 0 * * *'; // Run daily at midnight

Workflow Plugin

Orchestrate complex bot workflows:
npm install @commandkit/workflow
commandkit.config.ts
import { workflow } from '@commandkit/workflow';

export default defineConfig({
  plugins: [
    workflow(),
  ],
});

Legacy Plugin

Migrate from CommandKit v3 to v4:
npm install @commandkit/legacy
commandkit.config.ts
import { legacy } from '@commandkit/legacy';

export default defineConfig({
  plugins: [
    legacy(),
  ],
});

Creating custom plugins

Runtime plugin

Create a custom runtime plugin by extending RuntimePlugin:
plugins/analytics.ts
import { RuntimePlugin } from 'commandkit';
import type { CommandKitPluginRuntime } from 'commandkit';
import type { CommandKitEnvironment } from 'commandkit';

export class AnalyticsPlugin extends RuntimePlugin {
  async onBeforeCommandsLoad(ctx: CommandKitPluginRuntime) {
    console.log('Analytics: Tracking commands load');
  }
  
  async onAfterCommand(ctx: CommandKitPluginRuntime, env: CommandKitEnvironment) {
    const commandName = env.variables.get('commandName');
    console.log(`Analytics: Command executed - ${commandName}`);
  }
}
Use in config:
commandkit.config.ts
import { AnalyticsPlugin } from './plugins/analytics';

export default defineConfig({
  plugins: [
    new AnalyticsPlugin(),
  ],
});

Compiler plugin

Create a custom compiler plugin by extending CompilerPlugin:
plugins/transform.ts
import { CompilerPlugin } from 'commandkit';
import type { PluginTransformParameters, TransformedResult } from 'commandkit';

export class TransformPlugin extends CompilerPlugin {
  async transform(params: PluginTransformParameters): Promise<TransformedResult | null> {
    const { code, id } = params;
    
    // Skip non-command files
    if (!id.includes('/commands/')) {
      return null;
    }
    
    // Transform the code
    const transformedCode = code.replace(
      /console\.log/g,
      'Logger.log'
    );
    
    return {
      code: transformedCode,
      map: null,
    };
  }
}

Plugin lifecycle hooks

Runtime plugins can hook into various lifecycle events:
onBeforeCommandsLoad
(ctx: CommandKitPluginRuntime) => Promise<void>
Called before commands are loaded
onAfterCommandsLoad
(ctx: CommandKitPluginRuntime) => Promise<void>
Called after commands are loaded
onBeforeEventsLoad
(ctx: CommandKitPluginRuntime) => Promise<void>
Called before events are loaded
onAfterEventsLoad
(ctx: CommandKitPluginRuntime) => Promise<void>
Called after events are loaded
onBeforeClientLogin
(ctx: CommandKitPluginRuntime) => Promise<void>
Called before the Discord client logs in
onAfterClientLogin
(ctx: CommandKitPluginRuntime) => Promise<void>
Called after the Discord client logs in
onBeforeInteraction
(ctx: CommandKitPluginRuntime, interaction: Interaction) => Promise<void>
Called before each interaction is handled
onBeforeMessageCommand
(ctx: CommandKitPluginRuntime, message: Message) => Promise<void>
Called before each message command is processed
executeCommand
(ctx, env, source, command, execute) => Promise<boolean>
Intercept command execution. Return true to handle execution yourself.
async executeCommand(ctx, env, source, command, execute) {
  console.log('Before command execution');
  await execute();
  console.log('After command execution');
  return true; // We handled it
}
onAfterCommand
(ctx: CommandKitPluginRuntime, env: CommandKitEnvironment) => Promise<void>
Called after command and all deferred functions are executed
prepareCommand
(ctx, commands) => Promise<CommandBuilderLike | null>
Modify command data before registration
willEmitEvent
(ctx, event) => Promise<void>
Called before an event is emitted

Plugin configuration

Plugins can accept configuration options:
class MyPlugin extends RuntimePlugin<{ apiKey: string; timeout: number }> {
  constructor(options: { apiKey: string; timeout?: number }) {
    super({
      apiKey: options.apiKey,
      timeout: options.timeout ?? 5000,
    });
  }
  
  async onBeforeCommandsLoad(ctx) {
    console.log(`API Key: ${this.options.apiKey}`);
    console.log(`Timeout: ${this.options.timeout}`);
  }
}
Use with options:
commandkit.config.ts
export default defineConfig({
  plugins: [
    new MyPlugin({
      apiKey: process.env.API_KEY,
      timeout: 10000,
    }),
  ],
});

Plugin context

Plugins receive a CommandKitPluginRuntime context with access to:
async onAfterCommandsLoad(ctx: CommandKitPluginRuntime) {
  // Access CommandKit instance
  const commandkit = ctx.commandkit;
  
  // Access Discord client
  const client = commandkit.client;
  
  // Access loaded commands
  const commands = commandkit.commandHandler.loadedCommands;
  
  // Access configuration
  const config = commandkit.appConfig;
}

Best practices

1

Keep plugins focused

Each plugin should handle one concern (caching, logging, analytics, etc.)
2

Use TypeScript

Type your plugin options and use the provided types for hooks
3

Document your plugin

Provide clear documentation on how to use your plugin and its options
4

Handle errors gracefully

Plugins shouldn’t crash the bot. Catch and log errors appropriately
5

Test thoroughly

Test plugins with different configurations and edge cases

Example: Custom logging plugin

plugins/logger.ts
import { RuntimePlugin } from 'commandkit';
import type { CommandKitPluginRuntime, CommandKitEnvironment } from 'commandkit';
import type { Interaction, Message } from 'discord.js';

interface LoggerOptions {
  logFile?: string;
  logLevel?: 'info' | 'debug' | 'warn' | 'error';
}

export class LoggerPlugin extends RuntimePlugin<LoggerOptions> {
  private logFile: string;
  private logLevel: string;
  
  constructor(options: LoggerOptions = {}) {
    super(options);
    this.logFile = options.logFile ?? 'bot.log';
    this.logLevel = options.logLevel ?? 'info';
  }
  
  async onAfterClientLogin(ctx: CommandKitPluginRuntime) {
    const client = ctx.commandkit.client;
    this.log('info', `Bot logged in as ${client.user?.tag}`);
  }
  
  async onBeforeInteraction(
    ctx: CommandKitPluginRuntime,
    interaction: Interaction
  ) {
    if (!interaction.isCommand()) return;
    
    this.log('info', `Command: ${interaction.commandName} by ${interaction.user.tag}`);
  }
  
  async onAfterCommand(
    ctx: CommandKitPluginRuntime,
    env: CommandKitEnvironment
  ) {
    const commandName = env.variables.get('commandName');
    const duration = env.variables.get('duration');
    
    this.log('debug', `Command ${commandName} completed in ${duration}ms`);
  }
  
  private log(level: string, message: string) {
    if (this.shouldLog(level)) {
      const timestamp = new Date().toISOString();
      console.log(`[${timestamp}] [${level.toUpperCase()}] ${message}`);
      // Write to file logic here
    }
  }
  
  private shouldLog(level: string): boolean {
    const levels = ['debug', 'info', 'warn', 'error'];
    return levels.indexOf(level) >= levels.indexOf(this.logLevel);
  }
}
Use the custom plugin:
commandkit.config.ts
import { LoggerPlugin } from './plugins/logger';

export default defineConfig({
  plugins: [
    new LoggerPlugin({
      logFile: 'app.log',
      logLevel: 'debug',
    }),
  ],
});

Next steps

Configuration

Configure CommandKit options

Commands

Back to commands documentation

Build docs developers (and LLMs) love