Command diffing
Introduction
Section titled “Introduction”Every time your bot starts, it needs to make sure the slash commands registered with Discord match what’s defined in your code. The naive approach is to push all commands on every boot — but Discord’s REST API has rate limits, and pushing unchanged commands wastes that budget for no reason.
Warden’s command diffing system solves this by comparing your local command definitions against what’s currently registered with Discord. If nothing has changed, no API call is made. If a single option description changed on one command, only that command is updated. This makes “sync on boot” completely safe for production — even at scale.
How it works
Section titled “How it works”When scan() discovers your command classes, the framework builds a complete picture of your slash command tree — names, descriptions, options, permissions, localizations, and everything else that Discord needs. On boot (or when you run warden sync), this local picture is compared against the commands currently registered with Discord via the REST API.
The comparison happens in three stages:
-
Fetch — retrieve the currently registered commands from Discord
-
Diff — compare each local command against its registered counterpart
-
Push — only send commands that are new, changed, or removed
If the diff is empty — meaning your code matches what Discord already has — no PUT request is made at all. You’ll see this in the logs:
[info] Commands are up to date — skipping syncWhat gets compared
Section titled “What gets compared”The diff is granular. It doesn’t just check whether a command exists — it compares every property that Discord cares about:
| Property | Example change that triggers a sync |
|---|---|
name | Renaming /warn to /caution |
description | Changing “Warn a user” to “Issue a warning to a member” |
options | Adding a new option, changing an option’s type, reordering |
option descriptions | Changing “User to warn” to “The member to warn” |
option requirements | Making a previously optional field required |
choices | Adding a new severity level to a string choice |
permissions | Changing defaultMemberPermissions |
localizations | Adding or updating name/description translations |
subcommands | Adding, removing, or modifying subcommands |
subcommand groups | Restructuring the command tree |
Changes to your execute() method, middleware, or cooldowns do not trigger a sync — those are runtime concerns that Discord doesn’t know about.
Why this matters
Section titled “Why this matters”If you’re running a small bot in one server, you might not notice the difference. But as your bot grows, command diffing becomes increasingly important:
Rate limit budget
Section titled “Rate limit budget”Discord’s REST API has strict rate limits. Every unnecessary PUT request to the commands endpoint counts against your budget. With diffing, a bot that restarts ten times during a deploy pipeline makes zero API calls if the commands haven’t changed.
Startup speed
Section titled “Startup speed”Pushing commands to Discord takes time — especially for bots with many commands across global and guild-scoped registrations. Skipping the sync when nothing changed shaves seconds off your startup time.
Safe restarts
Section titled “Safe restarts”In production, your bot may restart for many reasons — deployments, scaling events, crash recovery. With diffing, these restarts are transparent to Discord. No duplicate registrations, no flickering commands, no wasted API calls.
Multiple instances
Section titled “Multiple instances”If you’re running multiple bot processes (for sharding or redundancy), only the first one needs to sync. The rest see that commands are already up to date and skip the API call entirely.
Manual sync with the CLI
Section titled “Manual sync with the CLI”While “sync on boot” is the default, you may prefer to sync commands as a separate step — for example, as part of a CI/CD pipeline. The warden sync console command does exactly this:
pnpm warden syncThis boots the framework (without connecting to the gateway), diffs your commands, and pushes any changes. It’s ideal for deploy scripts:
# In your CI/CD pipelinesteps: - run: pnpm build - run: pnpm warden sync - run: pnpm startYou can combine this with start({sync: false}) to ensure commands are only synced once during deploy, never on process start:
new Bot() .discover(d => d .interactions('./dist/{commands,buttons,modals,menus}/**/*.js') .events('./dist/events/**/*.js')) .intents(i => i.defaults()) .partials(p => p.defaults()) .start({sync: false});For more details on the sync CLI, see the Sync guide.
Cleaning up stale registrations
Section titled “Cleaning up stale registrations”When you switch between guild-scoped and global command registration — or when you remove commands from your codebase — stale registrations can linger in Discord. The warden sync:clean command removes them:
pnpm warden sync:cleanThis compares what’s registered with Discord against what’s defined in your code, and removes anything that shouldn’t be there. Common scenarios where this is useful:
- Switching from guild to global — guild-scoped commands remain registered even after you remove
.registerTo(). Users see duplicate commands. - Removing a command — if you delete a command class, the framework no longer knows about it, so the normal diff won’t remove it from Discord.
- Renaming a command — the old name stays registered alongside the new one.