Skip to main content
CommandKit provides a powerful async context system built on Node.js’s AsyncLocalStorage. This allows you to access execution context, CommandKit instances, and shared state from anywhere within your command or event handlers without explicitly passing them around.

Understanding CommandKit Context

The context system operates on two levels:
  1. Async Context - Tracks the current execution environment using Node.js async hooks
  2. Command/Event Context - Provides access to Discord.js objects, command metadata, and utilities

Async Context APIs

useEnvironment()

Returns the current CommandKit execution environment. Throws an error if called outside a CommandKit handler.
import { useEnvironment } from 'commandkit';

export const chatInput: ChatInputCommand = async (ctx) => {
  // Access the execution environment
  const env = useEnvironment();
  
  // Get execution type
  const type = env.getType(); // 'COMMAND_HANDLER'
  
  // Access shared variables
  env.variables.set('userId', ctx.interaction.user.id);
};
Source: packages/commandkit/src/context/async-context.ts:126

useStore()

Returns the shared data store (Discord.js Collection) for the current execution environment.
import { useStore } from 'commandkit';

export const chatInput: ChatInputCommand = async (ctx) => {
  const store = useStore();
  
  // Store and retrieve data
  store.set('lastCommand', ctx.commandName);
  const previous = store.get('lastCommand');
};
Source: packages/commandkit/src/context/async-context.ts:140

getCommandKit()

Retrieves the CommandKit instance. Can be called with strict parameter to throw error if not found.
import { getCommandKit } from 'commandkit';

// Returns CommandKit | undefined
const kit = getCommandKit();

// Throws error if not found
const kit = getCommandKit(true);

// Explicitly allow undefined
const kit = getCommandKit(false);
Source: packages/commandkit/src/context/async-context.ts:101-112

CommandKitEnvironment

The CommandKitEnvironment class represents the execution environment for commands and events.

Properties

interface CommandKitEnvironment {
  // The CommandKit instance
  readonly commandkit: CommandKit;
  
  // The command context (if in command handler)
  readonly context: Context | null;
  
  // Shared store for arbitrary data
  readonly store: Collection<any, any>;
  
  // Variables map for internal state
  readonly variables: Map<string, any>;
}

Methods

setContext(context)

Sets the command context for this environment.
env.setContext(ctx);
Source: packages/commandkit/src/context/environment.ts:81

getType()

Returns the environment type (e.g., ‘COMMAND_HANDLER’).
const type = env.getType();
// CommandKitEnvironmentType.CommandHandler
Source: packages/commandkit/src/context/environment.ts:117

registerDeferredFunction(fn)

Registers a function to run after command execution completes.
const id = env.registerDeferredFunction(async (env) => {
  console.log('Command finished!');
});
Source: packages/commandkit/src/context/environment.ts:156

getExecutionTime()

Returns the command execution time in milliseconds.
const duration = env.getExecutionTime();
console.log(`Executed in ${duration}ms`);
Source: packages/commandkit/src/context/environment.ts:229

Command Context

The Context class provides access to Discord.js objects and command metadata.

Key Properties

class Context<ExecutionMode> {
  // Discord.js objects
  readonly client: Client<true>;
  readonly guild: Guild | null;
  readonly channel: TextBasedChannel | null;
  readonly interaction: ChatInputCommandInteraction; // varies by mode
  readonly message: Message; // for message commands
  
  // CommandKit objects
  readonly commandkit: CommandKit;
  readonly command: LoadedCommand;
  
  // Shared store
  readonly store: Collection<any, any>;
  
  // Command info
  readonly commandName: string;
  readonly executionMode: ExecutionMode;
}

Type Guards

export const chatInput: ChatInputCommand = async (ctx) => {
  if (ctx.isInteraction()) {
    // ctx.interaction is available
  }
  
  if (ctx.isChatInputCommand()) {
    // Specifically a slash command
  }
  
  if (ctx.isMessage()) {
    // ctx.message is available
  }
};
Source: packages/commandkit/src/app/commands/Context.ts:428-470

Forwarding Commands

Forward execution to another command while preserving context.
export const chatInput: ChatInputCommand = async (ctx) => {
  // Forward to another command
  await ctx.forwardCommand('help');
  // This code won't execute
};
Source: packages/commandkit/src/app/commands/Context.ts:348

Deferred Functions

Schedule functions to run after command execution completes.

after(fn)

Register a function to run after the current command finishes.
import { after } from 'commandkit';

export const chatInput: ChatInputCommand = async (ctx) => {
  after((env) => {
    console.log('Cleanup after command');
    console.log(`Took ${env.getExecutionTime()}ms`);
  });
  
  // Command logic...
};
Source: packages/commandkit/src/context/environment.ts:253

cancelAfter(id)

Cancel a previously scheduled deferred function.
import { after, cancelAfter } from 'commandkit';

export const chatInput: ChatInputCommand = async (ctx) => {
  const id = after(() => {
    console.log('This might not run');
  });
  
  if (someCondition) {
    cancelAfter(id);
  }
};
Source: packages/commandkit/src/context/environment.ts:277

Context-Aware Functions

CommandKit automatically wraps your handlers in context-aware functions using makeContextAwareFunction. This ensures:
  1. Async context is properly propagated
  2. Errors are captured and handled
  3. Deferred functions run after execution
  4. Cleanup occurs properly
// Internal implementation
export function makeContextAwareFunction<R, F>(
  env: CommandKitEnvironment,
  fn: R,
  finalizer?: F
): R {
  return (...args: any[]) => {
    return provideContext(env, async () => {
      try {
        const result = await fn(...args);
        return result;
      } catch (e) {
        if (!isCommandKitError(e)) {
          env.setExecutionError(e);
        } else {
          throw e;
        }
      } finally {
        if (typeof finalizer === 'function') {
          await finalizer(...args);
        }
      }
    });
  };
}
Source: packages/commandkit/src/context/async-context.ts:60

Best Practices

  1. Use Context Parameter: Always prefer using the ctx parameter passed to your handlers rather than calling useEnvironment() directly.
  2. Store Scoping: Data in ctx.store is scoped to the current command execution, including all middlewares.
  3. Deferred Functions: Use after() for cleanup operations that should happen regardless of success or failure.
  4. Type Safety: Use specific context types like ChatInputCommandContext for better type inference.
import { ChatInputCommandContext } from 'commandkit';

export const chatInput = async (ctx: ChatInputCommandContext) => {
  // ctx has full type information
  await ctx.interaction.reply('Hello!');
};

Advanced Use Cases

Cross-Handler Data Sharing

Share data between commands and events using the CommandKit store.
// In a command
export const chatInput: ChatInputCommand = async (ctx) => {
  ctx.commandkit.store.set('lastInteraction', Date.now());
};

// In an event
export default async (ctx) => {
  const lastInteraction = ctx.commandkit.store.get('lastInteraction');
};

Performance Tracking

import { useEnvironment, after } from 'commandkit';

export const chatInput: ChatInputCommand = async (ctx) => {
  const env = useEnvironment();
  
  after((env) => {
    const duration = env.getExecutionTime();
    console.log(`${ctx.commandName} took ${duration}ms`);
    
    if (duration > 1000) {
      console.warn('Slow command detected!');
    }
  });
  
  // Command logic...
};

Custom Context Extensions

Extend context functionality using middleware.
export const middleware: Middleware = async (ctx) => {
  // Add custom data to store
  ctx.store.set('startTime', Date.now());
  ctx.store.set('userId', ctx.interaction.user.id);
  
  // Available in command handler
};

export const chatInput: ChatInputCommand = async (ctx) => {
  const startTime = ctx.store.get('startTime');
  const elapsed = Date.now() - startTime;
};

Build docs developers (and LLMs) love