createCommandHandler
Wire commands, validators, cooldowns, plugins, and storage into a discord.js client.
createCommandHandler(options)
function createCommandHandler(options: CommandHandlerOptions): CommandHandler;Subscribes the dispatcher to your discord.js Client and registers commands with Discord on clientReady. Returns a small handle for lifecycle management.
import { createCommandHandler } from "@djs-commands/core";
import { Client, GatewayIntentBits } from "discord.js";
import { storage } from "./storage";
const client = new Client({ intents: [GatewayIntentBits.Guilds] });
const handler = createCommandHandler({
client,
commands: [ping, help],
commandDir: "./src/commands",
storage,
cacheAdapter: redisCacheAdapter(redis),
});
await handler.ready;
await client.login(process.env.DISCORD_TOKEN);CommandHandlerOptions
interface CommandHandlerOptions {
client: Client;
commands?: AnyCommand[];
commandDir?: string;
eventDir?: string;
dev?: boolean;
botOwners?: readonly string[];
validators?: readonly Validator[];
canRunCommand?: CanRunCommand;
plugins?: PluginManifest[];
registration?: HandlerRegistrationConfig;
cacheAdapter?: CacheAdapter;
legacy?: HandlerLegacyConfig;
storage?: Storage;
storageFeatures?: StorageFeaturesConfig;
startupLog?: StartupLogConfig;
}| Field | Purpose |
|---|---|
client | discord.js Client instance — required. |
commands | Inline list of commands to dispatch. |
commandDir | Directory to recursively load commands from. Default exports must look like Commands. |
eventDir | Directory to recursively load EventDefinitions from. |
dev | When true (or NODE_ENV !== "production"), the command dir is watched and edited files hot-reload. |
botOwners | IDs that pass the built-in ownerOnly check. |
validators | Global validators run on every command. |
canRunCommand | Slimmer single-callback gate — return true / false / a reason string. |
plugins | Plugin manifests merged at boot. |
registration | Discord application command scopes the handler owns. Defaults to global sync; pass false to disable framework registration. |
cacheAdapter | TTL-native cache for cooldowns. Without it, cooldowns are in-memory. |
legacy | Master switch + default prefix for legacy invocation. |
storage | Persistence adapter for enabled framework storage features. |
storageFeatures | Opt into storage-backed framework features. guildPrefixes defaults to legacy.enabled; disabledCommands and channelLocks default to false. |
startupLog | Controls the DJS Commands boot summary printed after the client is ready and registration completes. Defaults to enabled. |
HandlerRegistrationConfig
type HandlerRegistrationConfig =
| false
| {
enabled?: boolean;
global?: "sync" | "clear" | "ignore" | { mode: "sync" | "clear" | "ignore" };
guilds?: readonly (string | { id: string; mode?: "sync" | "clear" | "ignore" })[];
};When registration is omitted, the handler syncs global commands. When you pass an object, only listed scopes are managed; omitted scopes are ignored.
sync overwrites that Discord scope with the current command list. clear overwrites it with an empty list. Both are destructive for that configured scope, and Discord commands persist until overwritten or cleared. Stopping your bot does not delete commands.
createCommandHandler({
client,
commandDir: "./src/commands",
registration: {
global: "clear",
guilds: [{ id: "123456789012345678", mode: "sync" }],
},
});Per-command overrides:
defineCommand({
name: "admin",
description: "Guild-only admin command",
registration: { global: false, guilds: ["123456789012345678"] },
run: async () => {},
});
defineCommand({
name: "runtime-only",
description: "Loaded for dispatch, not auto-registered",
registration: false,
run: async () => {},
});CommandHandler
interface CommandHandler {
ready: Promise<void>;
destroy: () => Promise<void>;
}| Field | Notes |
|---|---|
ready | Resolves once boot completes. Rejects if storage validation or plugin setup throws. Failed boot does not leave runtime listeners attached. |
destroy | Detaches all listeners, awaits each plugin's teardown in reverse-registration order, stops the fs watcher. Errors during teardown are logged but don't block other teardowns. Does not delete registered application commands from Discord. |
await handler.destroy();HandlerLegacyConfig
interface HandlerLegacyConfig {
enabled: boolean;
defaultPrefix: string;
}Master switch + fallback prefix for legacy mode. Per-guild prefix overrides come from the storage adapter (guild_prefix model — see Storage).
createCommandHandler({
client,
commands: [...],
legacy: { enabled: true, defaultPrefix: "!" },
storageFeatures: { guildPrefixes: false },
});StorageFeaturesConfig
interface StorageFeaturesConfig {
guildPrefixes?: boolean;
disabledCommands?: boolean;
channelLocks?: boolean;
}Only enabled features query storage. If an enabled feature needs an unmapped framework model, handler.ready rejects.
StartupLogConfig
type StartupLogConfig =
| boolean
| "box"
| "line"
| {
enabled?: boolean;
style?: "box" | "line";
};By default, the handler prints a DJS Commands startup summary after handler.ready, Discord clientReady, and command registration complete. TTY terminals get a boxed summary; CI and redirected logs get a compact single line.
╭─ DJS Commands ─────────────────────────╮
│ Version 3.0.1 │
│ Bot MyBot#1234 │
│ Commands 18 loaded │
│ Registration guild sync: 1 │
│ Legacy enabled, prefix "!" │
│ Storage configured │
│ Cache memory │
│ Dev hot reload enabled │
╰────────────────────────────────────────╯Disable it for fully custom logging:
createCommandHandler({
client,
commandDir: "./src/commands",
startupLog: false,
});Last updated on
