Shared code
Any folder ofsrc/ that the framework does not read is yours. The usual one is src/lib/. Put a function there, import it from a relative path, without extension:
src/lib/warnings.ts
src/commands/warnings.ts
src/lib/warnings.ts reloads every file that uses it.
A file that declares nothing is plain code: shared code can also sit next to the files that use it, src/moderation/checks.ts beside src/moderation/ban.ts. Pick one habit and keep it; src/lib/ is the simplest.
Keeping files short
A command file should read like a summary: check, do, answer. Whenrun grows past a screen, move the work to a function in src/lib/ and keep the file as the glue between Discord and that function. Two reasons:
- A function in
src/lib/can be tested or reused without Discord; aruncan’t. - The next person to read the file (you, in three months) sees what the command does without reading how.
Storing data
TheMap of warnings.ts above is fine for shared code, and wrong for data: it starts empty every time the bot starts, chapterjs dev reloads it on every save, and in production each process has its own. What must be remembered goes in a store, the previous chapter: export const warnings = store<number>(), kept in data/, per server, and shared by every process.
A store is for what fits in memory. A bot with millions of rows uses a database made for Node (SQLite through a package, or a server like PostgreSQL), kept in src/lib/ behind a few functions (getSettings(guildId), saveSettings(...)), so that the commands and events never know how it is stored.
A cooldown
“Not more than once a day per person” is a common need, and a command declares it withcooldown:
src/commands/daily.ts
/daily again too soon gets a private answer saying when they can (“You can use this command again 8 October 2026 17:12.”; a short cooldown reads “in 5 seconds”, counting down, and the answer goes away when the time comes), and run does not run. cooldown: { user: '10m', channel: '1m', guild: '1h' } makes a channel or a whole server wait too. Like the Map above, the last uses are kept in memory: they start empty when the bot starts. The details are in the Guide.
Grouping files
Where a file is and what it is called mean nothing to the framework: a command is a command because ofcommand(), wherever the file sits. So sort your files the way you think: a folder per kind (src/commands/, src/events/), or a folder per feature (src/moderation/, with its commands, its buttons and its events together, in one file or several), as the moderation template does. The tour shows both.
For commands, prefer commands (/ban, /kick) over subcommands (/mod ban, /mod kick), unless they are variants of one action (/timeout add, /timeout remove). Discord hides a whole command from people who lack its permissions, never one subcommand: with /mod ban and /mod kick, someone who may only kick still sees /mod ban.
What the framework does not do yet
ChapterJS is young, and some things a bot may want are not there. Knowing them saves a search:
The full list, kept up to date, is Roadmap.
Recap
- Shared code goes in
src/lib/(or next to the files that use it), imported without extension, and reloaded with the files that use it. - A
Mapis for shared code, not for data: what must be remembered goes in a store, and millions of rows in a database behind a few functions. - A cooldown is one setting of the command,
cooldown: '1d', kept in memory; a growing project sorts its files by feature, since the framework reads what they export, not where they are.
Memory and limits
What the bot remembers, and the limits of Discord it handles for you.
Guide: Roadmap
Everything the framework does not do, and what to do meanwhile.