> ## Documentation Index
> Fetch the complete documentation index at: https://www.chapterjs.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Commands

> Write a slash command as one file, choose where it works and who can use it, and see it appear in your test server

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:

```ts src/commands/ping.ts theme={"theme":{"light":"github-light","dark":"vesper"}}
import { command } from 'chapterjs';

export default command({
  name: 'ping',
  description: 'Replies with Pong!',
  async run({ interaction }) {
    await interaction.reply('Pong!');
  },
});
```

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.

> [!NOTE]
>
> Every field a command takes and every type it uses is in the [reference](https://www.chapterjs.org/reference/command.md).

## The name

`name` is what people type after the `/`. A subcommand is written with its parents, separated by spaces:

| `name` | Command |
| - | - |
| `'ping'` | `/ping` |
| `'mod ban'` | `/mod ban` |
| `'mod kick'` | `/mod kick` |
| `'mod roles add'` | `/mod roles add` |

* 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.

```text theme={"theme":{"light":"github-light","dark":"vesper"}}
✗ src/commands/add.ts The name "mod roles admin add" has 4 words: Discord allows a command, a group and a subcommand, so 3 at most ('mod roles add' is /mod roles add).
```

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:

```text theme={"theme":{"light":"github-light","dark":"vesper"}}
✗ src/commands/ping.ts "Ping" can't be in the name of a command: use lowercase letters, digits, - and _ only, 32 characters at most.
```

A command with subcommands (`/mod`) needs no declaration of its own: Discord asks for its description but never shows it, so ChapterJS generates one.

> [!WARNING]
>
> 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:
>
> ```text theme={"theme":{"light":"github-light","dark":"vesper"}}
> ✗ src/commands/mod.ts /mod can't be a command and have subcommands (src/commands/ban.ts is /mod ban): Discord only lets people use the subcommands. Make this one a subcommand too, for example name: 'mod run'.
> ```

### 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:

```text theme={"theme":{"light":"github-light","dark":"vesper"}}
✗ src/moderation/ban.ts (again) /ban is already declared in src/moderation/ban.ts: two commands can't have the same name.
```

### 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`:

```text theme={"theme":{"light":"github-light","dark":"vesper"}}
✗ src/commands/ping.ts This command has no description: write it in src/messages/en-US.ts (commands: { "ping": { description: '...' } }), or in the file (description: '...').
```

A bot in several languages can leave it out of the file and write it, with its translations, in the [language files](https://www.chapterjs.org/translated-messages.md#the-names-and-descriptions-of-commands) instead: one place or the other.

## What run receives

`run` receives one object. Take what you need from it:

```ts src/commands/whoami.ts theme={"theme":{"light":"github-light","dark":"vesper"}}
import { command } from 'chapterjs';

export default command({
  name: 'whoami',
  description: 'Says who you are',
  async run({ interaction, user, member, guild, channel }) {
    await interaction.reply(
      `You are ${user.username}, known as ${member.displayName} in ${guild.name}, in #${channel.name}.`
    );
  },
});
```

| Name | What it is |
| - | - |
| `interaction` | The use of the command: what to answer with. See [Answering](https://www.chapterjs.org/commands/answering.md). |
| `options` | What the person filled in, typed from what you declared. See [Options](https://www.chapterjs.org/commands/options.md). |
| `user` | Who used the command, as a [user](https://www.chapterjs.org/reference/user.md). |
| `member` | Who used the command, as a [member](https://www.chapterjs.org/reference/member.md) of the server. |
| `guild` | The [server](https://www.chapterjs.org/reference/guild.md) the command was used in. |
| `channel` | The [channel](https://www.chapterjs.org/reference/channels) the command was used in: always there, without a request, so you can `channel.send()` right away. |
| `t` | The texts of your bot in the language of who will read the answer, when the project declares a [language](https://www.chapterjs.org/translated-messages.md). |

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:

| `where` | The command is offered | `guild` and `member` | `channel` |
| - | - | - | - |
| `'guild'` (the default) | In servers | Always there | A channel of the server |
| `'dm'` | In private messages with your bot | Do not exist: your editor does not even offer them | The private conversation |
| `'both'` | In both | There or `null`: one `if (guild)` tells you, for everything at once | One or the other |

```ts src/commands/hello.ts theme={"theme":{"light":"github-light","dark":"vesper"}}
import { command } from 'chapterjs';

export default command({
  name: 'hello',
  description: 'Says hello',
  where: 'both',
  async run({ interaction, guild, member, channel }) {
    if (guild) {
      // In a server: the member and the channel are the ones of a server.
      await interaction.reply(`Hello ${member.displayName}, in #${channel.name}!`);
    } else {
      await interaction.reply('Hello! We are in private here.');
    }
  },
});
```

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.

> [!NOTE]
>
> 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:
>
> ```text theme={"theme":{"light":"github-light","dark":"vesper"}}
> ℹ src/commands/settings.ts /settings only works in private messages, and chapterjs dev only runs your bot in My Server. Try it with chapterjs start.
> ```
>
> A command with `where: 'both'` can only be tried in the server. To try them in private messages, run [`chapterjs start`](https://www.chapterjs.org/run/start).

## Who can use a command

`permissions` lists what a member needs to use the command:

```ts src/commands/purge.ts theme={"theme":{"light":"github-light","dark":"vesper"}}
import { command } from 'chapterjs';

export default command({
  name: 'purge',
  description: 'Deletes the last messages of this channel',
  permissions: ['ManageMessages'],
  options: {
    count: { type: 'integer', description: 'How many', min: 2, max: 100, required: true },
  },
  async run({ interaction, options, channel }) {
    const messages = await channel.fetchMessages({ limit: options.count });
    const deleted = await channel.bulkDelete(messages);
    await interaction.reply({ content: `Deleted ${deleted} messages.`, ephemeral: true });
  },
});
```

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](https://www.chapterjs.org/reference/permissions.md#permissionname): `['BanMembers']`, `['ManageMessages', 'ManageThreads']`... A name that does not exist is reported when the file loads.

> [!NOTE]
>
> 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:

```ts src/commands/daily.ts theme={"theme":{"light":"github-light","dark":"vesper"}}
import { command } from 'chapterjs';

export default command({
  name: 'daily',
  description: 'Claims your daily reward',
  cooldown: '1d',
  async run({ interaction }) {
    await interaction.reply('Here is your reward! 🎁');
  },
});
```

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:

```ts src/commands/announce.ts theme={"theme":{"light":"github-light","dark":"vesper"}}
import { command } from 'chapterjs';

export default command({
  name: 'announce',
  description: 'Pings everyone',
  permissions: ['MentionEveryone'],
  cooldown: { user: '10m', guild: '1h' },
  async run({ interaction }) {
    await interaction.reply('@everyone, something is happening!');
  },
});
```

| Key | Who waits |
| - | - |
| `user` | Each person, wherever they use the command. `cooldown: '10s'` is `cooldown: { user: '10s' }`. |
| `channel` | Each channel, whoever uses it. |
| `guild` | Each server, whoever uses it and wherever. Not for a command with `where: 'dm'`. |

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:

```text theme={"theme":{"light":"github-light","dark":"vesper"}}
✗ src/commands/daily.ts "cooldown" is "10 minutes", which is not a duration: cooldown: '10s' (a number and a unit: s, m, h or d, like '30s', '2h', '1d' or '1h30m') for each person, or cooldown: { user: '10s', channel: '5s', guild: '1m' } for each place.
```

> [!NOTE]
>
> A large bot runs on [several processes](https://www.chapterjs.org/run/start.md#large-bots), 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

| Field | Meaning |
| - | - |
| `nsfw: true` | Only show the command in age-restricted channels. |
| `ephemeral: true` | Make the answers of the command only visible to who used it. See [Answering](https://www.chapterjs.org/commands/answering.md#answers-only-the-person-sees). |
| `locales` | The command in other languages. See [Translations](https://www.chapterjs.org/commands/translations.md). |

## 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:

```text theme={"theme":{"light":"github-light","dark":"vesper"}}
⚠ src/commands/where.ts /where was used in a channel the bot can't answer in (type 4): it did not run.
```

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:

```text theme={"theme":{"light":"github-light","dark":"vesper"}}
✓ 3 commands, 2 events loaded
✓ Commands updated on My Server
```

On each save, they are registered again if they changed:

```text theme={"theme":{"light":"github-light","dark":"vesper"}}
↻ Reloaded in 12 ms, 3 commands, 2 events loaded
↻ Commands updated on My Server
```

If Discord refuses them, the bot keeps running with the commands Discord already has:

```text theme={"theme":{"light":"github-light","dark":"vesper"}}
✗ Discord refused the commands of your project: Discord refused PUT /applications/{application.id}/guilds/{guild.id}/commands: Invalid Form Body (50035)
```

### In production: everyone

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

```text theme={"theme":{"light":"github-light","dark":"vesper"}}
✓ Commands updated for everyone
```

### 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](https://www.chapterjs.org/run/start.md#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:

```text theme={"theme":{"light":"github-light","dark":"vesper"}}
ℹ /poll is not the same here as in production, so My Server shows it twice: yours, and the one everyone has. It is shown once again when production runs your version.
```

> [card] [Options](https://www.chapterjs.org/commands/options.md)
>
> What people fill in, typed for you.

> [card] [Answering](https://www.chapterjs.org/commands/answering.md)
>
> Reply, defer, edit, follow up, and what happens when your function fails.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.