Skip to content

Welcome

Warden is a Discord bot framework with expressive, elegant syntax. We’ve already laid the foundation — freeing you to create without wiring command handlers, plumbing interaction dispatch, or hand-rolling dependency injection on top of discord.js.

Yes, there are already other Discord frameworks. We’ve read them. We liked some of their ideas enough to borrow them, and disagreed with enough of the rest that we ended up here, writing a welcome page.

We believe development must be an enjoyable and creative experience to be truly fulfilling. Warden takes the pain out of bot development by easing common tasks used in the majority of Discord projects, such as:

  • A simple, decorator-driven command API.
  • A powerful middleware pipeline for cooldowns, permissions, and custom preconditions.
  • A first-class plugin system with adapters for Drizzle, Redis, and more.
  • Background jobs and scheduled tasks driven by cron expressions.
  • Graceful shutdown with in-flight interaction draining, because your bot shouldn’t drop a moderation action mid-deploy.
  • Hot module reloading in development.

Warden is accessible, yet powerful — providing the tools needed for large, robust bots while keeping the experience approachable for your very first /ping command.

There are a variety of Discord frameworks and libraries you can choose from when building a bot in TypeScript. Even so, we believe Warden is the best choice for writing modern, opinionated Discord applications.

Warden was extracted from a real, long-running Discord bot — not designed in a vacuum. Every opinion in the framework is load-bearing. The shutdown drain, the dependency injection container, the service provider lifecycle, the cooldown manager — all of it exists because we felt the pain of its absence while shipping a bot that real people use to argue about memes every day.

The plugin system is powerful enough that third-party authors can publish their own packages without forking the framework — a bar we set for ourselves on day one. This monorepo ships fourteen packages for a framework currently at version 0.0.0. We call this “ambition.” Laravel calls it “Tuesday.”

Decorate a class, save the file, and it’s registered. The framework takes care of command sync, interaction dispatch, and type-safe option building on your behalf. Tooling is a first-class citizen — warden make:command, warden sync, and the create-warden scaffolder are there from day one.

import {command, reply} from '@warden/core';
import type {Command} from '@warden/core';
import type {ChatInputCommandInteraction} from 'discord.js';
@command({name: 'ping', description: 'Check the bot is alive'})
export default class PingCommand implements Command {
public async execute(interaction: ChatInputCommandInteraction): Promise<void> {
await reply(interaction, 'Pong!');
}
}

That’s a complete, registered, auto-discovered slash command. No manifests. No registry arrays. No manual wiring. No registerApplicationCommands() override ceremony inherited from a base class you didn’t write.

Every framework has opinions. Here are ours, stated plainly.

Silly features deserve serious foundations

Section titled “Silly features deserve serious foundations”

Your bot will grow features you can’t anticipate. A karma engine. A birthday reminder. A meme leaderboard. A counter for every swear your friends have ever typed, with no UI to surface the data and no real plan for what to do with it. (That one is not hypothetical.) Warden treats each of them as a first-class citizen, with dependency injection, middleware, shutdown draining, and audit logging available from day one. The “fun” feature shouldn’t be the one that takes down production at 2am on a Saturday.

A decorated class is a registered class. A file in your scan path is a discovered file. There is one right way to define a command, and it looks the same as the one right way to define an event, a job, a button, or a modal. If you’ve seen one Warden handler, you’ve seen them all. We named the framework after a word meaning “guard” — we take consistency seriously enough to commit to the bit.

Your editor knows what an interaction is. Your editor knows what options your command accepts. You shouldn’t have to write as ChatInputCommandInteraction<'cached'> three times a file to get there.

When you’re writing business logic, Warden gets out of the way. No required base class. No mandatory lifecycle hooks. No manifest files to keep in sync with reality. Just a class, a decorator, and the handler you actually care about writing. We have all used the other kind of framework, and we have all filed the bug report at 11pm on a Sunday.

Commands are only one of the things you’ll build. Events and scheduled jobs follow the exact same pattern — same decorator shape, same injection ergonomics, same testing story.

An event handler:

import {event} from '@warden/core';
import type {Event} from '@warden/core';
import type {Message} from 'discord.js';
@event({name: 'WelcomeLogEvent', event: 'messageCreate'})
export default class WelcomeLogEvent implements Event<'messageCreate'> {
public async execute(message: Message): Promise<void> {
if (message.author.bot) return;
// do something with the message
}
}

A scheduled job:

import {job} from '@warden/core';
import type {Job} from '@warden/core';
@job({name: 'BirthdayReminders', schedule: '0 9 * * *'}) // 9am every day
export default class BirthdayRemindersJob implements Job {
public async execute(): Promise<void> {
// send birthday messages
}
}

Three different primitives. One mental model. This consistency is not an accident — we kept rewriting the handler shape until it stopped embarrassing us in code review, and then we rewrote it once more for good measure.

Warden is split across a set of focused packages. Install the pieces you need and ignore the rest.

PackagePurpose
@warden/coreThe framework — decorators, handlers, DI, middleware pipeline
@warden/consoleCLI tooling: make:command, sync, and friends
@warden/drizzleFirst-party Drizzle ORM integration (MySQL/MariaDB + SQLite)
@warden/cacheRedis-backed caching service (with drizzle-backed option)
@warden/jobsTyped background & scheduled jobs
@warden/permissionsPermission policies and guard decorators
@warden/auditAudit logging for moderation actions
@warden/i18nLocalization without ceremony
@warden/heartbeatUptime heartbeat pings
create-wardenProject scaffolder

Every plugin is just a service provider — there’s nothing magic happening in the first-party packages that you can’t do in your own.

If this list gives you the faint, unsettling feeling that you’re looking at the first-party package lineup of a certain PHP web framework, please try not to dwell on it. We certainly aren’t.

A quick note on the “we” throughout this page: Warden is, at the time of writing, maintained by one person — who has a day job and, on a good week, a social life. The royal “we” is a stylistic choice, not a team size. You’ll forgive the affectation.

Warden was, for most of its life, the src/util/ folder of a private friend-group Discord bot called wego-overseer — birthday reminders, a karma engine that refuses to stay simple, a brittle music queue, a swear counter logging every profane word typed since March 2024 (no UI; we just collect it, for reasons), seven meme generators, and a /braille translator whose error branch reads “should technically never happen but here we are.” The scaffolding was quietly doing more interesting work than the features sitting on top of it.

Every decorator, every middleware hook, every scheduled job in Warden has load-bearing ancestry in a feature from that bot — something probably shipped at 1am on a Sunday and reviewed by nobody. That split between silly feature and serious architecture is the entire point: let your silly features rest on serious foundations. A framework whose job is to make a karma personality engine easy to ship is a framework that takes moderation bots in its stride.

Warden did not emerge from a blank page. We’ve read the alternatives, stolen liberally from the good ideas, and tried to improve on the rough edges:

  • Laravel — for service providers, make:* tooling, and the insistence that developer experience is a feature, not a luxury.
  • discord-akairo — for pioneering filesystem-based auto-discovery. No longer maintained, which is, if we’re being honest, half the reason this framework exists. RIP to a real one.
  • Sapphire — for the precondition model and for carrying the akairo torch forward when akairo couldn’t.
  • Discordx — for committing fully to decorator-driven everything long before it was fashionable. Warden’s command decorator lineage runs most visibly through here.
  • Necord — for the decomposed-event pattern that Warden’s event decomposition engine is a direct descendant of.
  • NestJS — for showing that decorators and dependency injection can coexist with TypeScript’s type system without punishment.
  • discord.js — for existing, for being excellent, and for doing the low-level gateway work we happily build on top of.
  • Whoever first wrote dd() in Laravel — we ported it. We are not sorry. If you know, you know.

Warden stands on their shoulders. Any good idea you find here probably started somewhere else. Any bad one is uniquely our own.

Warden is at v0.x. The API is stabilizing quickly, but we reserve the right to break it before v1.0 — mostly in service of trimming, not adding. After v1.0: semantic versioning, migration guides, and a public deprecation policy. Very professional. Very responsible.

Code, issues, and discussions all live on github.com/getwarden/warden. PRs that come with tests and a short explanation of the problem being solved are the fastest way to our heart — singular, see above. Issues that open with “this is probably a dumb question” will be answered with dignity and grace.

Installation is a single command. Your first bot is a single file. You’ll be online in under a minute, at which point you’re free to go build your own karma personality engine. We encourage it.