Sage Mode
Automatic defer, error handling, and ephemeral replies for commands and context menus.
Sage mode automatically handles interaction deferral and error handling. When a command takes longer than a configurable buffer, Sage defers the interaction so Discord doesn't time out. It also catches errors and shows user-friendly messages.
How it works
When a command is invoked, Sage starts a race between your handler and a timer:
- Timer starts at
deferBuffermilliseconds (default: 250ms) - If your handler returns before the timer, the result is replied directly -- no defer needed
- If the timer fires first, the interaction is deferred automatically
- When your handler eventually returns, Sage edits the deferred reply with the result
This means fast commands feel instant (no "thinking..." message), while slow commands don't time out.
Sage also applies to context menu handlers (both user and message context menus), not just slash commands. Context menu handlers benefit from the same automatic deferral and error handling described above.
Sage's automatic deferral only activates for async handlers (functions that return a Promise). If your handler is synchronous and returns a value immediately, the defer mechanism is bypassed entirely -- the runtime checks whether the result is a Promise before scheduling a deferred reply.
Configuration
Global Sage options
Configure Sage globally in your plugin config:
Prop
Type
import type { DiscordConfig } from '@robojs/discordjs'
export default {
sage: {
defer: true,
deferBuffer: 250,
ephemeral: false,
errorReplies: true
}
} satisfies DiscordConfigexport default {
sage: {
defer: true,
deferBuffer: 250,
ephemeral: false,
errorReplies: true
}
}Per-command overrides
Override Sage for individual commands via their config.sage:
import type { CommandConfig } from '@robojs/discordjs'
export const config: CommandConfig = {
description: 'A secret command',
sage: {
ephemeral: true // only this command is ephemeral
}
}
export default () => 'This is a secret!'Disabling Sage
Disable Sage globally:
export default {
sage: false
}Or per-command:
export const config = {
sage: false
}
export default async (interaction) => {
await interaction.deferReply()
// Handle everything manually
await interaction.editReply('Done!')
}Resolution chain
Sage options are resolved with the following priority (highest first):
command.sage-- per-commandconfig.sageplugin.sage-- globalsagein plugin configplugin.timeouts.commandDeferral-- alias fordeferBuffer- Defaults --
{ defer: true, deferBuffer: 250, ephemeral: false, errorReplies: true }
Each level inherits unset values from the next. For example, if a command sets sage: { ephemeral: true } but doesn't set deferBuffer, it inherits deferBuffer from the plugin config or defaults.
Error handling
When Sage is enabled and a command throws an error:
- Development mode: The error's
.messageis sent as an ephemeral reply (stack traces are not included) - Production mode: No error reply is sent -- the error is silently swallowed from the user's perspective
- Set
errorReplies: falseto suppress error replies entirely, even in development mode
For richer error forwarding with full stack traces sent to a configurable debug channel, use the @robojs/dev plugin. That behavior is separate from Sage and works independently.
Ephemeral mode
When ephemeral: true, all deferred and replied messages are only visible to the user who triggered the interaction. This is useful for sensitive commands or admin tools.
export default {
sage: {
ephemeral: true
}
}