DJS Commandsv3 docs
API Reference@djs-commands/core

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;
}
FieldPurpose
clientdiscord.js Client instance — required.
commandsInline list of commands to dispatch.
commandDirDirectory to recursively load commands from. Default exports must look like Commands.
eventDirDirectory to recursively load EventDefinitions from.
devWhen true (or NODE_ENV !== "production"), the command dir is watched and edited files hot-reload.
botOwnersIDs that pass the built-in ownerOnly check.
validatorsGlobal validators run on every command.
canRunCommandSlimmer single-callback gate — return true / false / a reason string.
pluginsPlugin manifests merged at boot.
registrationDiscord application command scopes the handler owns. Defaults to global sync; pass false to disable framework registration.
cacheAdapterTTL-native cache for cooldowns. Without it, cooldowns are in-memory.
legacyMaster switch + default prefix for legacy invocation.
storagePersistence adapter for enabled framework storage features.
storageFeaturesOpt into storage-backed framework features. guildPrefixes defaults to legacy.enabled; disabledCommands and channelLocks default to false.
startupLogControls 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>;
}
FieldNotes
readyResolves once boot completes. Rejects if storage validation or plugin setup throws. Failed boot does not leave runtime listeners attached.
destroyDetaches 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

On this page