Middlewares
Middlewares in CommandKit allow you to execute code before and after command execution. This is incredibly powerful for implementing features like logging, authentication, permission checks, or any other cross-cutting concerns for your Discord bot.
CommandKit supports three different types of middleware files, each serving different scoping purposes for your commands.
Basic middleware structure
All middleware files follow the same export pattern. You can export
beforeExecute and afterExecute functions that will run at their
respective times during command execution.
- TypeScript
- JavaScript
import { MiddlewareContext } from 'commandkit';
export function beforeExecute(ctx: MiddlewareContext) {
// This function will be executed before the command is executed
console.log(`User ${ctx.interaction.user.id} is about to execute a command`);
}
export function afterExecute(ctx: MiddlewareContext) {
// This function will be executed after the command is executed
console.log(
`Command execution completed for user ${ctx.interaction.user.id}`,
);
}
/**
* @typedef {import('commandkit').MiddlewareContext} MiddlewareContext
*/
/**
* Executed before command execution
* @param {MiddlewareContext} ctx - The middleware context
*/
export function beforeExecute(ctx) {
// This function will be executed before the command is executed
console.log(`User ${ctx.interaction.user.id} is about to execute a command`);
}
/**
* Executed after command execution
* @param {MiddlewareContext} ctx - The middleware context
*/
export function afterExecute(ctx) {
// This function will be executed after the command is executed
console.log(
`Command execution completed for user ${ctx.interaction.user.id}`,
);
}
Stop command execution
You can stop a command from running by calling stopMiddlewares() in
the beforeExecute function.
- TypeScript
- JavaScript
import { type MiddlewareContext, stopMiddlewares } from 'commandkit';
export function beforeExecute(ctx: MiddlewareContext) {
if (ctx.interaction.user.id !== '1234567890') {
// Conditionally stop command execution
console.log(`${ctx.commandName} will not be executed!`);
stopMiddlewares();
}
// Continue with command execution
console.log(`${ctx.commandName} will be executed!`);
}
/**
* @typedef {import('commandkit').MiddlewareContext} MiddlewareContext
*/
/**
* @param {MiddlewareContext} ctx - The middleware context
*/
export function beforeExecute(ctx) {
if (ctx.interaction.user.id !== '1234567890') {
// Conditionally stop command execution
console.log(`${ctx.commandName} will not be executed!`);
stopMiddlewares();
}
// Continue with command execution
console.log(`${ctx.commandName} will be executed!`);
}
You can also use stopMiddlewares() inside a command function to stop
any afterExecute middlewares from running.
In addition, you can also use stopMiddlewares() inside any
afterExecute middleware function to stop any remaining middlewares
from running.
Calling stopMiddlewares() in a try/catch block may lead to
unexpected behavior.
If you still want to use stopMiddlewares() in a try/catch block, you
can use the isErrorType function to check if the error is an
instance of the CommandKitErrorCodes.StopMiddlewares error.
- TypeScript
- JavaScript
import { type MiddlewareContext, stopMiddlewares } from 'commandkit';
export function beforeExecute(ctx: MiddlewareContext) {
try {
// code that may throw an error
stopMiddlewares(); // conditionally stop the middleware chain
} catch (error) {
if (isErrorType(error, CommandKitErrorCodes.StopMiddlewares)) {
// if stopMiddlewares() is called in the try block, throw it so CommandKit can stop the middleware chain
throw error;
}
// this means that the code threw the error, and stopMiddlewares() was not called
// the rest of the middlewares will be executed as normal
console.error(error);
}
}
/**
* @typedef {import('commandkit').MiddlewareContext} MiddlewareContext
*/
/**
* @param {MiddlewareContext} ctx - The middleware context
*/
export function beforeExecute(ctx) {
try {
// code that may throw an error
stopMiddlewares(); // conditionally stop the middleware chain
} catch (error) {
if (isErrorType(error, CommandKitErrorCodes.StopMiddlewares)) {
// if stopMiddlewares() is called in the try block, throw it so CommandKit can stop the middleware chain
throw error;
}
// this means that the code threw the error, and stopMiddlewares() was not called
// the rest of the middlewares will be executed as normal
console.error(error);
}
}
Middleware types
Directory-scoped middleware
Create a +middleware.ts file in any commands directory to apply
middleware to commands whose definition files live in that same
directory.
- TypeScript
- JavaScript
import type { MiddlewareContext } from 'commandkit';
export function beforeExecute(ctx: MiddlewareContext) {
// This middleware will run before commands defined in this directory
if (!ctx.interaction.member.permissions.has('KickMembers')) {
throw new Error('You need moderation permissions to use this command');
}
}
/**
* @typedef {import('commandkit').MiddlewareContext} MiddlewareContext
*/
/**
* @param {MiddlewareContext} ctx - The middleware context
*/
export function beforeExecute(ctx) {
// This middleware will run before commands defined in this directory
if (!ctx.interaction.member.permissions.has('KickMembers')) {
throw new Error('You need moderation permissions to use this command');
}
}
For hierarchical command trees, "current directory" means the directory that contains the leaf definition:
- a shorthand leaf such as
add.subcommand.tsuses middleware from the directory that contains that file - a folder leaf such as
[archive]/command.tsuses middleware from the[archive]directory - parent command and group directories do not automatically contribute
+middleware.tsfiles to deeper leaves
Command-specific middleware
For command-specific middleware, create a file named
+<command-name>.middleware.ts where <command-name> matches your
command file name.
- TypeScript
- JavaScript
import type { MiddlewareContext } from 'commandkit';
export function beforeExecute(ctx: MiddlewareContext) {
// This middleware only runs before the ban command
console.log('Ban command is about to be executed');
}
/**
* @typedef {import('commandkit').MiddlewareContext} MiddlewareContext
*/
/**
* @param {MiddlewareContext} ctx - The middleware context
*/
export function beforeExecute(ctx) {
// This middleware only runs before the ban command
console.log('Ban command is about to be executed');
}
Global middleware
Create a +global-middleware.ts file in your commands directory to
apply middleware to every command in your entire Discord bot.
- TypeScript
- JavaScript
import type { MiddlewareContext } from 'commandkit';
export function beforeExecute(ctx: MiddlewareContext) {
// This middleware runs before ANY command in your bot
console.log(
`Command executed by ${ctx.interaction.user.tag} in ${ctx.interaction.guild?.name || 'DMs'}`,
);
}
/**
* @typedef {import('commandkit').MiddlewareContext} MiddlewareContext
*/
/**
* @param {MiddlewareContext} ctx - The middleware context
*/
export function beforeExecute(ctx) {
// This middleware runs before ANY command in your bot
console.log(
`Command executed by ${ctx.interaction.user.tag} in ${ctx.interaction.guild?.name || 'DMs'}`,
);
}
Middleware execution order is deterministic:
- global middlewares run first
- current-directory middlewares run second
- same-directory command-scoped middlewares run last