Library / SDK
sapphiredev/framework avatar
sapphiredev/framework

Sapphire and the pieces based structure behind a discord.js bot

Discord bot framework built on top of discord.js for advanced and amazing bots.

757 stars77 forksTypeScriptMIT

At a glance

What is it?
Sapphire is a TypeScript Discord bot framework layered on discord.js that turns commands, listeners, preconditions and plugins into loadable pieces with a store, rather than a hand written event handler. It suits bot developers who want structure, and it suits them less if one small command does not need a framework underneath it.
Who is it for?
Sapphire fits a developer or a team writing a bot that will grow past a single command, because the store, the pieces model and the plugin system are what keep a larger bot from turning into one file of handlers. It is the wrong choice for a one-shot bot that answers a single command, where plain discord.js is less code and one fewer dependency.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository received new commits within the last day.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Everything is a piece, and pieces live in a store

Sapphire is a Discord bot framework built on top of discord.js, written in TypeScript, and its central idea is the piece.

The feature list names the pieces of the model directly: a Command Handler, Arguments, Pre-conditions and a Listeners Store. Each of those is a category of piece, and a piece is a self-contained unit that the framework loads, registers and dispatches for you. A command is a piece. A listener is a piece. A precondition is a piece. The store is the mechanism that holds them: the Listeners Store, and the corresponding stores for commands and preconditions, are where the framework keeps what it found on disk and what it will run when something happens.

This is the difference from a hand written bot. The usual way to build a bot is to create the client, attach an event listener for `messageCreate`, and branch on the content. That works until the bot has twenty commands, then the branch becomes a switch statement, and the switch statement becomes a file nobody wants to touch. The piece model pushes each command into its own module with a name, a description and a run method, and lets the framework do the matching and the dispatch.

Two other entries in the feature list describe the edges of that model. Pre-conditions are the guards, so a command can declare that it needs a permission or a role and the framework refuses the call before your code runs. Arguments are the parsing layer, so the values a user typed arrive as typed values rather than as a string you parse yourself. The framework also claims full TypeScript and JavaScript support, which means a bot can be written in either without giving up the typed pieces model.

The dependency list explains what the framework is actually made of

The runtime dependencies in the package manifest are more informative than the marketing line, because they show the framework is a set of focused parts rather than one large piece of code.

The `@sapphire/pieces` package at `^4.4.1` is the piece system itself, and `@sapphire/lexure` at `^1.1.12` is the argument parser. `@sapphire/result` at `^2.8.0` is a Result type, which is the mechanism for expressing an operation that can fail without throwing, so a piece can return a failure the framework renders. `@sapphire/ratelimits` at `^2.4.11` handles rate limiting, and `@sapphire/stopwatch` at `^1.5.4` is timing.

The Discord specific parts are `@sapphire/discord.js-utilities` at `^7.3.3` and `@sapphire/discord-utilities` at `^4.0.0`, with `@discordjs/builders` at `^1.13.0` for building message payloads, and `@sapphire/utilities` at `^3.18.2` for general helpers. The general helpers matter more than they sound, because the pieces model leans on them for things like the message and channel abstractions a piece needs.

Everything is scoped to the `@sapphire` namespace except the Discord builders package, which is a sign that the team publishes the pieces as separate packages and treats the framework as the assembly of them. That also means a bot that needs one of those parts can take it directly without taking the framework, which is a reasonable design and one that keeps the dependency surface honest.

The manifest declares the package as MIT licensed, published as `@sapphire/framework`.

Installing means taking discord.js 14 with it

The installation instruction is short, and it includes a warning that is easy to skim past. The documentation says the package depends on `discord.js` and to be sure to install it alongside, then gives the command:

bash
npm install @sapphire/framework [email protected]

The `[email protected]` part is the significant detail. Discord.js 14 is a major version with breaking changes, and the documentation pins the range rather than letting you take whatever is latest. That is a deliberate choice by the framework, and it means your dependency on discord.js is decided by Sapphire's compatibility rather than by your own appetite for upgrades.

The documentation also notes you can replace `npm install` with your package manager of choice, which is a small but useful statement given the repository itself is a Yarn project with a `.yarn/` directory and a `.yarnrc.yml` at the root.

Two things the installation does not cover are worth naming. First, there is no mention of a bot token or a client ID in the install step, so the credential setup lives elsewhere, in the documentation site linked from the header rather than in the repository. Second, this is a library install, not a project scaffold: installing the package gives you a dependency, not a bot project with a client, a command folder and a piece directory already in place. The repository is the framework, not a template for a bot, so the first steps after installing are yours to define.

For the first real use, the shape is: install, create a client from the framework, and add your first command piece. The framework handles the rest of the wiring once the pieces exist.

Plugins are how a Sapphire bot grows without a fork

The feature list claims complete modularity and extendability, advanced plugins support, and support for many plugins hosted in a separate `sapphiredev/plugins` repository. That plugin system is the part of the design that determines whether the framework stays useful as a bot grows.

A plugin, in this model, is a packaged piece of behaviour that the framework can pick up. So a rate limiter, a permissions checker or a logging setup arrives as a plugin rather than as code you copy into your own repository. The dependency list backs this up: `@sapphire/ratelimits` is a runtime dependency, and `@sapphire/pieces` is the substrate that plugins register into. Plugins are not a side channel bolted on afterwards; they use the same piece mechanism as your own commands.

The difference in approach between a plugin and a copy-pasted module is ownership. A copied module becomes your code, to be patched when discord.js changes. A plugin is a versioned dependency that someone else updates, and your bot inherits that update along with the risk that it changes behaviour you relied on.

The same reasoning applies to the pre-conditions and arguments systems. Both are extension points, and both mean a bot can be assembled from pieces the maintainers wrote rather than from logic you wrote, which is precisely the value proposition and precisely the thing to check before committing to it: whether the plugins available cover the behaviour you need, or whether you will end up writing a plugin yourself and running that maintenance yourself.

The framework also generates its own API documentation, with a `docs` script that runs a typedoc JSON parser and a `typedoc.json` configuration at the root.

A dual format build, because CJS and ESM both still matter

The package manifest shows a build designed around the fact that Node still runs two module systems in practice.

The main entry point is `dist/cjs/index.cjs` and the module entry is `dist/esm/index.mjs`, with type declarations at `dist/cjs/index.d.cts`. The exports map then splits the two import styles cleanly: the `import` condition points at `./dist/esm/index.mjs` with its own `./dist/esm/index.d.mts` types, and the `require` condition points at `./dist/cjs/index.cjs` with `./dist/cjs/index.d.cts` types.

That is a lot of ceremony for a Discord bot library, and it is there because a bot that is a dependency of another package, or that is bundled, or that is run under a test runner, can be in either world. The `.cjs` and `.mts` extensions for the type files are what make the types resolve correctly under `moduleResolution` in a TypeScript project that uses `require`, which is a common enough configuration that getting it wrong produces confusing errors.

The build pipeline in the scripts section matches the same intent. The `build` script runs `tsdown`, and the `prepack` script runs the build and then two type bundlers in parallel, one producing the CommonJS output with `.cts` typings and one producing the ESM output with `.mts` typings, using `rollup-type-bundler` for each.

The rest of the scripts show a project with a conventional toolchain: Vitest for tests, ESLint and Prettier with shared `@sapphire` configurations, `tsc` for typechecking, and `cliff-jumper` for changelog generation and version bumps, with a `check-update` dry run that shows the version bump before it happens. The repository also carries `sonar-project.properties`, so static analysis results are tracked.

Where a framework is the wrong amount of structure

There is a clear case where Sapphire is more than you need, and it is worth saying plainly because the framework's own tagline is about pleasantness, not minimalism.

If your bot has one command, or a handful of commands that never change, then the store, the piece resolution, the pre-conditions and the plugin system are all overhead. You would be adding a framework, its nine runtime dependencies, and a second major version to track, in order to replace a few lines of an event handler. The dependency count is the argument here rather than any claim about code quality: a plain discord.js bot needs one dependency, and it needs a specific major version of it.

The other case is a bot whose behaviour is genuinely custom in the middle. The framework is opinionated about structure, which is the point, but it means that a bot with unusual dispatch requirements, unusual argument shapes, or a need to bypass the piece lifecycle will end up fighting the framework. That is a real cost of a batteries-included design.

There is also a version consequence to plan for. The framework pins `[email protected]` in its own install command, and a major discord.js release will not be usable with this version of Sapphire until the framework updates. Your upgrade path for a Sapphire bot is therefore gated on Sapphire's release, not yours. That is fine if you accept it, and annoying if your bot needs a discord.js feature the pinned major does not have.

The plugin route is the escape valve here, and also the risk. Writing your own plugin keeps you inside the framework's conventions, but you are then maintaining a package against discord.js's breaking changes, which is the work the framework was supposed to save you.

Release timing and the distance between versions

The release history is worth reading before you pin anything.

The three most recent releases are v5.4.0 on 2025-11-08, v5.5.0 on 2025-12-24, and v5.5.1 on 2026-08-22. The manifest version matches the newest release at 5.5.1, and the last push to the repository was on 2026-10-18, so there are commits after the current tag.

The shape of that timeline is uneven. A minor release came after about seven weeks, and the patch that followed took eight months. That gap is normal for a stable library with a small team, and it also tells you that patch releases are not on a schedule you can plan around. If you want a fix, you may be waiting for the next release, and there is no monthly cadence to point at.

The bump tooling in the repository explains how versions move. The `bump` script runs `cliff-jumper`, which derives the version and the changelog from conventional commits, and the `check-update` script runs the same tool in dry-run mode so you can see the proposed bump before applying it. The `CHANGELOG.md` and the `cliff.toml` configuration at the root are part of that flow, and there is a `.cliff-jumperrc.yml` for the tool's settings. The `cliff.toml` file also shows a git-cliff configuration, and the `.npm-deprecaterc.yml` file configures the package deprecation tool, which is how the project retires old majors when a new one lands.

On the licence side, the manifest records MIT and the repository carries a `LICENSE.md`. The donation section is worth a glance too: the project states it stays open source regardless of donations and accepts money through Open Collective, Ko-fi, PayPal, Patreon and GitHub Sponsorships, which tells you the funding model is community rather than commercial.

Editorial conclusion

Sapphire fits a developer or a team writing a bot that will grow past a single command, because the store, the pieces model and the plugin system are what keep a larger bot from turning into one file of handlers. It is the wrong choice for a one-shot bot that answers a single command, where plain discord.js is less code and one fewer dependency. Before adopting, check three things: that you can live with the discord.js version the install pins, since the documented command asks for `[email protected]`, that the plugin list covers the behaviour you would otherwise write yourself, and that the release cadence suits you, since v5.5.0 landed on 2025-12-24 and v5.5.1 on 2026-08-22 with the last push on 2026-10-18. The code is MIT licensed and published to npm as @sapphire/framework.

Frequently asked questions

What is Sapphire and what does it add to discord.js?

Sapphire is a Discord bot framework built on top of discord.js and written in TypeScript. It provides a Command Handler, Arguments, Pre-conditions and a Listeners Store, so commands, listeners and preconditions are loaded as pieces rather than wired by hand.

How do I install the Sapphire framework?

Run `npm install @sapphire/framework [email protected]`. The documentation notes that discord.js must be installed alongside the framework, and that you can replace `npm install` with your package manager of choice.

What are the runtime dependencies of @sapphire/framework?

They are `@sapphire/pieces`, `@sapphire/lexure`, `@sapphire/result`, `@sapphire/ratelimits`, `@sapphire/stopwatch`, `@sapphire/utilities`, `@sapphire/discord-utilities`, `@sapphire/discord.js-utilities` and `@discordjs/builders`. The pieces and lexure packages are the piece system and the argument parser respectively.

How does Sapphire handle plugins?

Plugins are packaged behaviour that plugs into the same piece mechanism as your own commands, and the project maintains a separate plugins repository with many of them. Advanced plugin support and a completely modular, extendable design are both listed as features.

What licence is the Sapphire framework released under?

The package manifest records the licence as MIT, and the repository contains a LICENSE.md file. The project states that the community remains open source regardless of whether it receives donations.

Official sources

  1. License: MIT
  2. Project website
  3. README
  4. Releases
  5. sapphiredev/framework on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/sapphiredev-framework.svg)](https://hysenlabs.com/projects/sapphiredev-framework)

Was this useful?