Skip to main content
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. Every template but minimal has its texts in two languages (en-US and fr), a presence and a test file. 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: 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:
src/moderation/ban.ts
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.
How to write each kind of declaration is in Commands, Events, Buttons and Tasks.

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.
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:
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, 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 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. A sound of public/ is played in a voice channel the same way, with voice.play(asset('intro.ogg')): see Playing in voice.

.chapterjs/: generated, ignored, safe to delete

This folder is written by ChapterJS, and git ignores it. It holds: 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.

.env

Two values, read by chapterjs dev; chapterjs start only needs the first one:
.env
Where to find them is in Your Discord application. 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.
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: 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. 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.
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:
pnpm-workspace.yaml

Commands

A slash command, options typed for you.

Events

A reaction to anything that happens on Discord.