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

# Project structure

> What each folder and file of your project is for, and how the framework finds what your files declare

A project made with `create chapterjs`, from the `minimal` template, looks like this. Everything your bot does lives in `src/`; the rest is configuration you rarely touch.

**my-bot**

**.chapterjs**

**public**

**src**

**presence.ts**

**.env**

**.env.example**

**.gitignore**

**package.json**

**tsconfig.json**

## Templates

`create chapterjs` offers five templates. `minimal` is preselected: one file, and the rest of this site to grow it. The others are complete bots: read them once you know the framework, or start from one.

| Template | What it is |
| - | - |
| `minimal` | One file, `src/presence.ts`: the text Discord shows under the name of your bot. Nothing else. |
| `tickets` | Support tickets in private threads: `/ticket panel` posts a button, which opens a form, which opens a thread with a claim button for the staff and a close button; `/ticket add` brings someone in. |
| `moderation` | `/warn add`, `/warn list` and `/warn clear` (forgotten after 30 days), `/timeout`, `/untimeout`, `/kick`, `/ban`, `/purge`, `/slowmode`, `/lock` and `/unlock`, and a log of every action in `#mod-log`. |
| `levels` | XP for every message, once a minute per person, a message and a role at each new level, `/rank` (and the same in the menu of a person), `/leaderboard` page after page, and `/xp set` for the staff. |
| `giveaways` | `/giveaway start` posts a giveaway people enter with a button; a task draws the winners when time is up, and the menu of its message ends it early or draws again. `/giveaway list` shows the ones running. |

Every template but `minimal` has its texts in two languages (`en-US` and `fr`), a presence and a [test file](https://www.chapterjs.org/testing.md). `levels` needs the Message Content Intent, to hear every message: `chapterjs dev` says where to enable it.

## `src/`: what a file exports says what it is

ChapterJS never asks you to register anything. It loads every file of `src/`, wherever it is and whatever it is called, and runs what the files **export**, made with one of its functions:

| Declared with | What it is |
| - | - |
| `command({ name, ... })` | A slash command: `name` is what people type after the `/`. `'ping'` is `/ping`, `'mod ban'` is `/mod ban` |
| `event({ name, ... })` | One reaction to an event: `name` is the event, `'memberJoin'` runs when someone joins |
| `button({ ... })`, `select({ ... })`, `modal({ ... })`, `embed({ ... })` | A button, a select menu, a form or an embed, told apart by **the name of its export**: `export const again = button(...)` is the button `again` |
| `task({ ... })` | Something the bot does by itself, every so often or at given times, named after its export |
| `presence({ ... })` | What the bot shows under its name: its status and what it is doing, declared once |
| `language({ locale, ... })` | One language of the bot: `locale` says which one; every function then receives `t` |

All are imported from the same place, and a file may declare as many as you like. The `tickets` template puts everything about tickets in `src/tickets/`: the commands, the form, the buttons and their texts, and its `tickets.ts` declares both a store and an event. A file that declares several things looks like this:

```ts src/moderation/ban.ts theme={"theme":{"light":"github-light","dark":"vesper"}}
import { button, command, event } from 'chapterjs';

export default command({
  name: 'ban',
  description: 'Bans a member',
  options: {
    target: { type: 'user', description: 'Who to ban', required: true },
  },
  async run({ interaction, options }) {
    await interaction.reply({
      content: `Ban ${options.target.displayName}?`,
      components: [confirmBan({ userId: options.target.id })],
    });
  },
});

export const confirmBan = button({
  label: 'Ban',
  style: 'danger',
  data: { userId: 'string' },
  async run({ interaction, data, guild }) {
    await guild.ban(data.userId, { reason: 'Banned with /ban' });
    await interaction.update({ content: 'Banned.', components: [] });
  },
});

export const logged = event({
  name: 'banAdd',
  run({ user, guild }) {
    console.log(`${user.username} was banned from ${guild.name}`);
  },
});
```

Any file that exports none of these is yours: `src/lib/greet.ts`, `src/db/index.ts`, whatever you like. It is loaded with the others, and your files import it as usual.

> [!NOTE]
>
> How to write each kind of declaration is in [Commands](https://www.chapterjs.org/commands.md),
> [Events](https://www.chapterjs.org/events.md), [Buttons](https://www.chapterjs.org/components.md) and [Tasks](https://www.chapterjs.org/tasks.md).

## Names

Two things name themselves, because Discord shows them or because they are the thing itself: a command (`name: 'ping'`) and an event (`name: 'memberJoin'`). Everything else is named by its export: `export const again = button(...)` is the button `again`, `export const report = task(...)` the task `report`. A default export takes the name of its file: `export default button(...)` in `src/again.ts` is `again` too.

Discord sends the name of a button back with every click, so renaming the export of a button breaks the buttons already on Discord (they answer "This button is not available any more."); moving the file changes nothing.

> [!WARNING]
>
> Two things can't have the same name: two commands named `ping`, or two
> buttons exported as `confirm`. The second one is reported with both files
> and left out.

## Imports

Your files are TypeScript, run by Node directly. A relative import needs no extension, and a folder with an `index` file works too:

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

export default command({
  name: 'hello',
  description: 'Says hello',
  async run({ interaction, user }) {
    db.visits += 1;
    await interaction.reply(greet(user.displayName));
  },
});
```

```ts src/lib/greet.ts theme={"theme":{"light":"github-light","dark":"vesper"}}
export function greet(name: string): string {
  return `Hello ${name}!`;
}
```

```ts src/db/index.ts theme={"theme":{"light":"github-light","dark":"vesper"}}
export const db = { visits: 0 };
```

Writing the extension works as well: `'../lib/greet.ts'`.

Packages from `node_modules` are imported as usual. Every file of `src/` is watched: in [`chapterjs dev`](https://www.chapterjs.org/run/dev), saving `src/lib/greet.ts` reloads every file that uses it. A test next to your code (`greet.test.ts`, `greet.spec.ts`) is never loaded by the bot: [`chapterjs test`](https://www.chapterjs.org/testing) runs it.

## `public/`: the files your bot sends

Pictures, documents, sounds: put them in `public/`, in any folders you like, and send one with `asset('photos/monday.png')` in the `files` of a message. Your editor lists the files of the folder as you type, and `chapterjs dev` keeps that list up to date while you add files. A file is read when the message is sent. See [Files](https://www.chapterjs.org/messages.md#files). A sound of `public/` is played in a voice channel the same way, with `voice.play(asset('intro.ogg'))`: see [Playing in voice](https://www.chapterjs.org/voice.md).

## `.chapterjs/`: generated, ignored, safe to delete

This folder is written by ChapterJS, and git ignores it. It holds:

| | What it is |
| - | - |
| `types/` | What your project adds to the types of `chapterjs`: the files of `public/` that `asset()` offers, your languages, which type `t`, and your commands. Your editor reads it. |
| `cache/` | What was last registered on Discord, so starting your bot again costs no request. |
| `build/` | Your compiled bot, written by [`chapterjs build`](https://www.chapterjs.org/run/build). |

It is written when you install your project (`postinstall` runs `chapterjs sync`) and every time `chapterjs dev` or `chapterjs build` starts. If your editor does not know the files of `public/` or your texts, run `chapterjs sync`: see the [CLI reference](https://www.chapterjs.org/reference/cli.md#chapterjs-sync).

## `.env`

Two values, read by [`chapterjs dev`](https://www.chapterjs.org/run/dev); [`chapterjs start`](https://www.chapterjs.org/run/start) only needs the first one:

```bash .env theme={"theme":{"light":"github-light","dark":"vesper"}}
BOT_TOKEN=
DEV_GUILD_ID=
```

Where to find them is in [Your Discord application](https://www.chapterjs.org/setup/discord-application.md). The real environment wins over the file: on a host, you give `BOT_TOKEN` in its settings and upload no `.env`. Two more, `REDIS_URL` and `REDIS_PASSWORD`, only matter when a store keeps its data in Redis: see [Storage](https://www.chapterjs.org/storage.md#in-redis).

> [!WARNING]
>
> Your token is a password: never share it, and never commit `.env`. Projects
> created with `create chapterjs` already ignore it in git.

## `package.json`

The scripts of your project run the [CLI](https://www.chapterjs.org/reference/cli.md):

| Script | Runs | When |
| - | - | - |
| `dev` | `chapterjs dev` | While you write your bot |
| `build` | `chapterjs build` | Before putting it online |
| `start` | `chapterjs start` | On the machine that hosts it |
| `test` | `chapterjs test` | To check every file, and your scenarios, without Discord |
| `typecheck` | `tsc -b` | To check the types of the whole project from a terminal |
| `postinstall` | `chapterjs sync` | By itself, after each install |

`chapterjs` is the only dependency of your bot, at the exact version `create chapterjs` had (`"chapterjs": "0.3.0"`, no `^`): your project only changes version when you run the command [`chapterjs dev` gives you](https://www.chapterjs.org/run/dev#a-new-version-of-chapterjs). TypeScript and `@types/node` are development dependencies: they check your code, and are not needed to run it.

## `tsconfig.json`

It only holds compiler options and one reference to `./.chapterjs`, which is where the real project is: every file of `src/`, plus what the framework writes about your project. Keep it that way.

> [!WARNING]
>
> A plain `tsc` checks nothing in a ChapterJS project. Use the `typecheck`
> script, which runs `tsc -b`: that is what your editor follows too.

## With pnpm: one more file

pnpm refuses to install a project that has esbuild somewhere in its dependencies (here, through `chapterjs`) until it is told whether esbuild's install script may run. It may not, and esbuild does not need it. `create chapterjs` writes the answer for you; in a project made another way, create it next to `package.json`:

```yaml pnpm-workspace.yaml theme={"theme":{"light":"github-light","dark":"vesper"}}
allowBuilds:
  esbuild: false
ignoredBuiltDependencies:
  - esbuild
```

> [card] [Commands](https://www.chapterjs.org/commands.md)
>
> A slash command, options typed for you.

> [card] [Events](https://www.chapterjs.org/events.md)
>
> A reaction to anything that happens on Discord.


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