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

# Errors

> Catch what is thrown when Discord refuses an action, cannot be reached, or rejects the token, and know what the person sees

An action that fails throws an error. There are three kinds, all exported by `chapterjs`, and you can tell them apart with `instanceof`.

> [!NOTE]
>
> What happens when your function throws without catching is in [Answering](https://www.chapterjs.org/commands/answering.md) for commands and in [Events](https://www.chapterjs.org/events.md) for events: the bot never stops, you get the error in your terminal with the file and the line.

```ts theme={"theme":{"light":"github-light","dark":"vesper"}}
import { DiscordApiError, type GuildMember } from 'chapterjs';

async function warn(member: GuildMember, text: string) {
  try {
    await member.send(text);
    return true;
  } catch (error) {
    // 50007: this user does not accept private messages.
    if (error instanceof DiscordApiError && error.code === 50007) return false;
    throw error; // anything else is a real problem
  }
}
```

## DiscordApiError

Discord answered a request with an error.

It is thrown when Discord refuses what the bot asked: a missing permission, something that no longer exists, a value it does not accept. The request was received and understood; retrying it gives the same answer until something changes.

**name** `'DiscordApiError'` *required*

What `instanceof` tells, as text.

**message** `string` *required*

What happened, with the reason Discord gave and the fields it refused: `Discord refused POST /channels/{channel.id}/messages: Missing Permissions (50013).`, then what it means in plain words for the codes a bot meets most (a missing permission, something deleted, private messages closed), and one line per refused field when there are some.

**status** `number` *required*

The HTTP status of the response (403, 404...).

**code** `number` *required*

The JSON error code of Discord (50013 for "Missing Permissions"...), or 0 when the response had none.

**fieldErrors** `readonly DiscordFieldError[]` *required*

The fields of the request Discord refused, when it says which.

**method** `'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'` *required*

The HTTP method of the request that failed.

**route** `string` *required*

The path of the endpoint as written in the documentation.

### Codes you will meet

Test `error.code`, never `error.message`: the text may change, the code does not.

| Code | Discord says | Usually means |
| - | - | - |
| `10003` | Unknown channel | The channel was deleted |
| `10007` | Unknown member | They left the server |
| `10008` | Unknown message | The message was deleted |
| `10026` | Unknown ban | The user is not banned |
| `50001` | Missing access | The bot can't see this channel or server |
| `50007` | Cannot send messages to this user | Their private messages are closed, or they share no server with the bot |
| `50013` | You lack permissions to perform that action | The bot lacks a permission, or its highest role is too low |
| `50035` | Invalid form body | A value was refused: look at `fieldErrors` |

The full list is in the [Discord documentation](https://docs.discord.com/developers/topics/opcodes-and-status-codes#json-json-error-codes).

### DiscordFieldError

One problem Discord found in a field of the request.

**path** `string` *required*

Where the problem is: `embeds[0].title`, or empty (`''`) for the whole request.

**code** `string` *required*

The kind of problem, as Discord names it (`BASE_TYPE_MAX_LENGTH`).

**message** `string` *required*

What is wrong, in English.

```ts theme={"theme":{"light":"github-light","dark":"vesper"}}
import { DiscordApiError, type TextChannel } from 'chapterjs';

async function post(channel: TextChannel, title: string) {
  try {
    await channel.send({ embeds: [{ title }] });
  } catch (error) {
    if (error instanceof DiscordApiError && error.code === 50035) {
      for (const field of error.fieldErrors) console.log(`${field.path}: ${field.message}`);
      return;
    }
    throw error;
  }
}
```

[Discord documentation](https://docs.discord.com/developers/reference#error-messages)

## DiscordUnavailableError

A request could not reach Discord, or Discord kept failing.

It is thrown when there is no answer to get: no internet connection, an outage of Discord, a server error that persisted. The bot already retried before throwing it; retrying later may work.

**name** `'DiscordUnavailableError'` *required*

What `instanceof` tells, as text.

**message** `string` *required*

`Discord could not be reached for GET /guilds/{guild.id}. Check your internet connection and https://discordstatus.com, then try again.`

**method** `'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'` *required*

The HTTP method of the request that failed.

**route** `string` *required*

The path of the endpoint as written in the documentation.

**cause** `unknown`

The underlying error, when Node gave one.

## InvalidTokenError

The bot token was refused: no request can succeed until it is fixed.

It is thrown by every request once Discord refused the token: the bot stops sending anything, so a wrong token never floods Discord. Nothing in your code can fix it; `chapterjs dev` checks the token before connecting, so you normally see it there (see [Troubleshooting](https://www.chapterjs.org/run/troubleshooting.md)).

**name** `'InvalidTokenError'` *required*

What `instanceof` tells, as text.

**message** `string` *required*

`Discord refused the bot token. Copy it again from the Developer Portal (your application → Bot → Reset Token) into BOT_TOKEN in your .env file.`

## Catching

Catch only what you expect, and let the rest through: an error you don't handle is reported with its file and line, which is what you want for a bug.

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

export default command({
  name: 'kick',
  description: 'Kick a member',
  options: { target: { type: 'user', description: 'Who to kick', required: true } },
  permissions: ['KickMembers'],
  async run({ interaction, guild, member, options }) {
    try {
      await guild.kick(options.target, `Kicked by ${member.displayName}`);
      await interaction.reply(`${options.target} was kicked.`);
    } catch (error) {
      if (error instanceof DiscordApiError && error.code === 50013) {
        await interaction.reply({ content: 'My role is below theirs, so I cannot kick them.', ephemeral: true });
        return;
      }
      throw error;
    }
  },
});
```

## What the person sees

When `run` throws and you did not catch, the person who used the command gets a short, private answer, never the technical details:

```text theme={"theme":{"light":"github-light","dark":"vesper"}}
Something went wrong while running this command.
```

When the error is a `DiscordApiError` with code `50013` or `50001`, the answer says what is missing instead:

```text theme={"theme":{"light":"github-light","dark":"vesper"}}
I don't have the permission to do that here.
```

The error itself, with its file and line, goes to your terminal. See [Answering](https://www.chapterjs.org/commands/answering.md). Both phrases can be translated in the language files of the project: see [What the bot answers by itself](https://www.chapterjs.org/translated-messages.md#what-the-bot-answers-by-itself).

## What never throws

Rate limits are not errors: when the bot goes too fast, requests wait for their turn and then succeed. A temporary failure of Discord is retried before anything is thrown.


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