djs-commandsv2 docs
Recipes

Recipes

Copy-paste solutions for common bot patterns.

Recipes are small, focused, copy-paste-friendly examples. They build on the Concepts pages — read those first if a snippet's import doesn't ring a bell.

The runnable examples/ directory in the repo has full bots you can clone and run. The minimal, components-v2-showcase, and moderation-drizzle examples each ship a working .env.example and package.json.

Defer a long-running command

If your handler does work that takes more than 3 seconds, defer the reply so Discord doesn't time out the interaction.

defineCommand({
	name: "report",
	description: "Generate the weekly report",
	run: async ({ interaction, reply }) => {
		if (interaction) await interaction.deferReply();
		const report = await generateReport(); // takes 8 seconds
		await reply(report);
	},
});

Role-gated command

Use the built-in roles field for the simple case, or a custom Validator if the rule is dynamic.

defineCommand({
	name: "kick",
	description: "Kick a user",
	guildOnly: true,
	permissions: ["KickMembers"],
	roles: ["123456789012345678"], // moderator role
	options: { target: { type: "user", description: "Who to kick", required: true } },
	run: async ({ options, member, reply }) => {
		const guildMember = await member?.guild.members.fetch(options.target.id);
		await guildMember?.kick();
		await reply(`Kicked ${options.target.tag}`);
	},
});

Per-user cooldown

defineCommand({
	name: "daily",
	description: "Claim your daily reward",
	cooldown: { type: "perUser", duration: 24 * 60 * 60 * 1000 },
	run: async ({ author, reply }) => {
		await reply(`Reward claimed for ${author.tag}.`);
	},
});

For distributed cooldowns across multiple bot processes, register the Redis cache adapter.

import { Modal, TextInput, renderModal } from "@djs-commands/jsx";

defineCommand({
	name: "feedback",
	description: "Send feedback",
	run: async ({ interaction }) => {
		await interaction.showModal(
			renderModal(
				<Modal title="Send feedback" customId="feedback-modal">
					<TextInput customId="subject" label="Subject" style="short" required={true} />
					<TextInput customId="body" label="Tell us more" style="paragraph" />
				</Modal>
			)
		);
	},
});

// Wire submission handling on the discord.js client itself:
client.on("interactionCreate", async (i) => {
	if (!i.isModalSubmit() || i.customId !== "feedback-modal") return;
	const subject = i.fields.getTextInputValue("subject");
	const body = i.fields.getTextInputValue("body");
	// store, log, post to a channel...
	await i.reply({ content: "Thanks!", ephemeral: true });
});

Toggling commands per guild

disabledCommands is a built-in storage model. The dispatcher consults it automatically — you only need to expose admin commands to flip the flag.

import { defineCommand, disableCommand, enableCommand } from "@djs-commands/core";

defineCommand({
	name: "toggle",
	description: "Enable or disable a command in this guild",
	guildOnly: true,
	permissions: ["ManageGuild"],
	options: {
		command: { type: "string", description: "Command name", required: true },
		state: { type: "string", description: "on or off", choices: ["on", "off"] as const, required: true },
	},
	run: async ({ options, guild, reply }) => {
		if (!guild) return;
		const fn = options.state === "off" ? disableCommand : enableCommand;
		await fn(storage, guild.id, options.command);
		await reply(`\`${options.command}\` is now ${options.state}`);
	},
});

Channel-locked commands

Use channels for a static allow-list, or the dynamic channel_locks storage model for per-guild moderator control.

import { lockCommandToChannel } from "@djs-commands/core";

await lockCommandToChannel(storage, guildId, "music-play", musicChannelId);
// `music-play` now only runs in `musicChannelId` for this guild

The dispatcher consults channel locks alongside the static channels field.

Hot-reload commands in development

Pass a commandDir to the handler. In dev (NODE_ENV !== "production") the directory is watched and edits hot-reload without a restart.

createCommandHandler({
	client,
	commandDir: "./src/commands",
});

To opt out, pass dev: false.

Want a recipe that's not here?

Open an issue with the recipes label or drop a request in the Discord support server.

On this page