Skip to main content
A slash command is what people type after / in Discord. To create one, declare it with command() in any file of src/, and export it:
src/commands/ping.ts
That is all: /ping shows up in your test server as soon as you save, and your run function runs when someone uses it. Nothing to register, no list to keep up to date.
Every field a command takes and every type it uses is in the reference.

The name

name is what people type after the /. A subcommand is written with its parents, separated by spaces:
  • One word is a command.
  • Two words make a subcommand of the command named by the first.
  • Three make a group. Discord allows nothing deeper: a fourth word is reported.
Names follow the rules of Discord: lowercase letters, digits, - and _, 32 characters at most. A command that breaks them is reported when its file loads:
A command with subcommands (/mod) needs no declaration of its own: Discord asks for its description but never shows it, so ChapterJS generates one.
A command with subcommands can’t be used by itself: with 'mod ban', there is no /mod. A command named 'mod' next to it is reported and left out:

Organising your files

Where a file is and what it is called mean nothing to the command: src/commands/ban.ts, src/moderation/ban.ts or a file that declares /ban next to the button it shows are all the same /ban. A file may declare several commands, each exported under its own name. Two declarations of the same command are reported, and the second one is left out:

A command or a subcommand?

Prefer commands (/ban, /kick), sorted in the folders you like, and keep subcommands for the variants of one action (/timeout add, /timeout remove). The reason is how Discord hides commands: it shows or hides a whole command, never one subcommand. With /mod ban and /mod kick, someone who may only kick still sees /mod ban; ChapterJS refuses it when they use it, but with /ban and /kick they would not see it at all.

The description

description is the text Discord shows under the name of the command, 1 to 100 characters. It is the only field a command needs besides run:
A bot in several languages can leave it out of the file and write it, with its translations, in the language files instead: one place or the other.

What run receives

run receives one object. Take what you need from it:
src/commands/whoami.ts
In a server, member, guild and channel are always there: no ?. to write. The interaction knows them too (interaction.guild, interaction.member, interaction.channel), and the language of the person (interaction.locale).

Servers and private messages

A command works in servers. where changes that, and what run receives follows:
src/commands/hello.ts
The same goes for interaction.guild, interaction.member and interaction.channel. ChapterJS makes sure of it when the command is used: a command of servers used in private, or the other way around, is refused with a private answer (“This command can only be used in a server.” or “This command can only be used in a private message with me.”), and your function does not run.
With chapterjs dev, your bot only runs in your test server. A command with where: 'dm' can’t be tried there, and chapterjs dev tells you so once per file:
A command with where: 'both' can only be tried in the server. To try them in private messages, run chapterjs start.

Who can use a command

permissions lists what a member needs to use the command:
src/commands/purge.ts
Discord hides the command from members who lack the permissions, and ChapterJS checks again when it is used: someone without them gets a private answer saying which ones are missing (“You need the Manage Messages permission to use this command.”), and your function does not run. Server admins can change who sees the command in the settings of their server. The names are the ones of Permissions: ['BanMembers'], ['ManageMessages', 'ManageThreads']… A name that does not exist is reported when the file loads.
A command with subcommands is shown to the members who have what every subcommand asks for; each subcommand then checks its own permissions when it is used.

Cooldowns

cooldown is how long a person waits before using the command again:
src/commands/daily.ts
A duration is a number and a unit, s, m, h or d, 1 second at least; parts add up ('1h30m'). Someone who uses the command too soon gets a private answer saying when they can (“You can use this command again in 5 minutes.”, with a time Discord shows in their language and counts down), and your function does not run. The answer deletes itself when the cooldown ends, so it never reads “5 minutes ago”. A cooldown of more than 15 minutes, which outlives what Discord lets the bot delete, shows the date and time instead (“again 8 October 2026 17:12”). Each person has their own count, and each command too. A cooldown can also be for a channel or a server, whoever used it; the three can be combined:
src/commands/announce.ts
When several apply, the person is told the one that ends last. A use that was refused counts for nothing: the first use decides when the next one may happen. The last uses are kept in memory: they start empty when the bot starts, and chapterjs dev keeps them through a save. A duration that can’t be read is reported when the file loads:
A large bot runs on several processes, each with its own memory. Everything that happens in a server goes to the same process, so the cooldowns of a channel or a server always hold; the one of a person holds in each process, so someone may use the command once in a server of one process and once in a server of another.

Other settings

Where the bot can’t answer

Discord can offer a command in a channel the bot can’t send messages to (a channel it does not see, for example). The command then does not run: the person reads “This command can not be used here.”, and your terminal says which command and where:
The same happens in a server the bot is not in. A command that Discord still offers while its file is gone or broken answers “This command is not available right now.”

How commands reach Discord

You never register anything: ChapterJS tells Discord about your commands, and only when something people see changed (a name, a description, an option, a translation, where it works). Changing what a command does never costs a request, and neither does starting your bot again.

In dev: your test server only

chapterjs dev registers your commands on your test server only, where they show up at once:
On each save, they are registered again if they changed:
If Discord refuses them, the bot keeps running with the commands Discord already has:

In production: everyone

chapterjs start registers them for everyone, in every server the bot is in and in private messages:

Dev and production

A command for everyone belongs to the bot, not to a server: Discord shows it in every server the bot is in, your test server included, and gives no way to hide it in one of them. So use a second application for development from the start, with its own token on your computer: your test server then only shows the commands of your files, never one twice. How to set it up is in A second bot for development. With one bot for both, chapterjs dev does what it can: it only adds to your test server the commands that are new or different in your project, and a command used there always runs the code on your computer. But while you are changing a command that production already has, your test server shows it twice (yours, and the one everyone has), and chapterjs dev tells you once:

Options

What people fill in, typed for you.

Answering

Reply, defer, edit, follow up, and what happens when your function fails.