Skip to content

Permissions

Before your bot performs a sensitive action, you probably want to make sure the person running the command actually has permission to do it. And equally important — you want to make sure the bot itself has the right permissions in Discord to carry out the action.

The @permissions() decorator handles both sides of this check in a single, declarative line. You tell Warden what Discord permissions are required, and the framework verifies them before your execute() method is ever called. If either the user or the bot is missing a required permission, Warden responds with a clear error message and moves on. No manual checking, no try-catch around permission errors, no half-executed commands.

The @permissions() decorator accepts an object with user and/or bot arrays. Each array contains Discord permission strings:

import {command, Command, permissions, CommandInteraction, Params} from '@warden/core';
@command({ name: 'ban', description: 'Ban a member from the server' })
@permissions({ user: ['BanMembers'], bot: ['BanMembers'] })
export default class BanCommand implements Command {
public async execute(interaction: CommandInteraction, params: Params): Promise<void> {
// This only runs if both the user AND the bot have BanMembers permission
}
}

That’s the whole pattern. One decorator, and your permission logic is handled.

The user array specifies which Discord permissions the person running the interaction must have. You can require a single permission or multiple:

// User must be able to moderate members
@permissions({ user: ['ModerateMembers'] })
// User must have BOTH of these permissions
@permissions({ user: ['ManageMessages', 'ManageChannels'] })

When you specify multiple permissions, the user must have all of them. This is an AND check — not OR.

The bot array works the same way, but checks the bot’s own permissions in the guild. This is critical for any bot, because there’s nothing worse than your bot telling a user “Done!” and then silently failing because it didn’t have the required permission:

@command({ name: 'mute', description: 'Timeout a member' })
@permissions({ bot: ['ModerateMembers'] })
export default class MuteCommand implements Command {
public async execute(interaction: CommandInteraction, params: Params): Promise<void> {
// Warden has already confirmed the bot can actually moderate members
}
}

By checking bot permissions upfront, you avoid confusing error states and can give the server admin a clear message about what’s missing.

Of course, most moderation commands need both — the user should be authorized, and the bot should be capable:

@command({ name: 'kick', description: 'Kick a member from the server' })
@permissions({ user: ['KickMembers'], bot: ['KickMembers'] })
export default class KickCommand implements Command {
public async execute(interaction: CommandInteraction, params: Params): Promise<void> {
// Both the invoking user and the bot have KickMembers
}
}

You can require different permissions for each side. For example, maybe only users with ManageGuild should be able to configure your bot’s auto-moderation, but the bot itself needs ManageMessages to do its job:

@command({ name: 'automod-setup', description: 'Configure auto-moderation rules' })
@permissions({
user: ['ManageGuild'],
bot: ['ManageMessages', 'ManageChannels'],
})
export default class AutomodSetupCommand implements Command {
public async execute(interaction: CommandInteraction, params: Params): Promise<void> {
// User can manage the guild, bot can manage messages and channels
}
}

The @permissions() decorator isn’t limited to slash commands. It works on every interaction type Warden supports:

InteractionDecoratorPermissions Work?
Slash commands@command()Yes
Buttons@button()Yes
Modals@modal()Yes
Select menus@menu()Yes
Context menus@context()Yes

This is particularly useful for buttons. Imagine you have a “Ban” button on an embed that displays reported users. You want to make sure only moderators can click it:

import {button, Button, permissions, Params} from '@warden/core';
import {ButtonInteraction} from 'discord.js';
@button({ customId: 'ban-reported-user' })
@permissions({ user: ['BanMembers'], bot: ['BanMembers'] })
export default class BanReportedUserButton implements Button {
public async execute(interaction: ButtonInteraction, params: Params): Promise<void> {
// Only users with BanMembers can use this button
}
}

When a permission check fails, Warden sends an ephemeral reply to the user explaining exactly what’s missing. Your execute() method is never called — the framework short-circuits the interaction entirely.

The error messages are clear and specific:

  • If the user is missing permissions: “You need the following permissions to use this: Ban Members”
  • If the bot is missing permissions: “I need the following permissions to do this: Ban Members, Manage Messages”

This means you never have to write permission-check boilerplate inside your commands. The decorator handles everything, and the user gets an immediate, understandable response.

Here’s a typical permission setup:

import {command, Command, permissions, cooldown, CommandInteraction, Params, Embed, reply} from '@warden/core';
@command({ name: 'ban', description: 'Permanently ban a member' })
@permissions({ user: ['BanMembers'], bot: ['BanMembers'] })
@cooldown({ duration: 5 })
export default class BanCommand implements Command {
public async execute(interaction: CommandInteraction, params: Params): Promise<void> {
const target = interaction.options.getUser('user')!;
const reason = interaction.options.getString('reason') ?? 'No reason provided';
await interaction.guild!.members.ban(target, { reason });
await reply(interaction, Embed.success(`${target.tag} has been banned. Reason: ${reason}`));
}
}
@command({ name: 'purge', description: 'Delete messages in bulk' })
@permissions({ user: ['ManageMessages'], bot: ['ManageMessages'] })
@cooldown({ duration: 10, scope: 'channel' })
export default class PurgeCommand implements Command {
public async execute(interaction: CommandInteraction, params: Params): Promise<void> {
const count = interaction.options.getInteger('count')!;
const channel = interaction.channel!;
await channel.bulkDelete(count);
await reply(interaction, Embed.success(`Deleted ${count} messages.`));
}
}
@command({ name: 'slowmode', description: 'Set channel slowmode' })
@permissions({ user: ['ManageChannels'], bot: ['ManageChannels'] })
export default class SlowmodeCommand implements Command {
public async execute(interaction: CommandInteraction, params: Params): Promise<void> {
const seconds = interaction.options.getInteger('seconds')!;
const channel = interaction.channel!;
await channel.setRateLimitPerUser(seconds);
await reply(interaction, Embed.success(`Slowmode set to ${seconds} seconds.`));
}
}

Context menu commands (right-click actions) benefit from permission checks too. Here’s a “Warn User” context menu that only moderators can use:

import {context, ContextMenu, permissions, Params, Embed, reply} from '@warden/core';
import {UserContextMenuCommandInteraction} from 'discord.js';
@context({ name: 'Warn User', type: 'user' })
@permissions({ user: ['ModerateMembers'] })
export default class WarnUserContext implements ContextMenu {
public async execute(interaction: UserContextMenuCommandInteraction, params: Params): Promise<void> {
const target = interaction.targetUser;
// ... save warning to database ...
await reply(interaction, Embed.success(`${target.tag} has been warned.`));
}
}

The @permissions() decorator covers Discord’s built-in permission system, and that’s often all you need. But what if you want more granular control? Maybe you have “Junior Moderator” and “Senior Moderator” roles in your bot’s own configuration, and only Senior Moderators should be able to use /ban.

That’s where the @warden/permissions plugin comes in. It adds a @gate() decorator that lets you define custom authorization boundaries based on your bot’s own role system, database-driven permissions, or any arbitrary logic you need.

import {gate} from '@warden/permissions';
@command({ name: 'ban', description: 'Permanently ban a member' })
@gate('moderation.ban')
export default class BanCommand implements Command {
// ...
}

The @warden/permissions plugin integrates seamlessly with the core @permissions() decorator — you can use both on the same command if you want to check Discord permissions AND your custom authorization layer.

For full details on custom roles, gates, and policy-based authorization, see the @warden/permissions plugin documentation.