Permissions And Middleware
Overview
The high-level middleware package provides reusable authorization, validation, guild, role, owner, and cooldown guards. It works with slash, context-menu, and prefix commands. The permissions.Permission type is a 64-bit bitfield; combine flags with permissions.Build or permissions.NewBuilder.
Architecture
Slash-command middleware receives *bot.InteractionContext. Member permissions come from the interaction's Member.Permissions, while bot permissions come from the interaction's AppPermissions string. Prefix middleware receives a *bot.MessageContext; RequirePrefixPermissions looks up the author through the configured cache.
Middleware wraps a handler. Router middleware runs for all slash commands, command middleware runs for one command, and prefix middleware applies to one prefix command. Authorization middleware responds ephemerally when it rejects a slash interaction.
Quick Start
This complete program protects /audit with member and bot permissions.
package main
import (
"log"
"os"
"github.com/discord-go/discord.go/bot"
"github.com/discord-go/discord.go/intents"
"github.com/discord-go/discord.go/permissions"
)
func main() {
token := os.Getenv("DISCORD_TOKEN")
if token == "" {
log.Fatal("DISCORD_TOKEN is required")
}
router := bot.NewRouter()
router.Command("audit", "Show an audit entry", func(ctx *bot.InteractionContext) {
if err := ctx.Reply("Authorized"); err != nil {
log.Printf("reply: %v", err)
}
}).
Use(bot.GuildOnly()).
RequirePermissions(permissions.ViewAuditLog).
RequireBotPermissions(permissions.ViewAuditLog)
b := bot.New(token, bot.WithIntents(intents.Guilds), bot.WithRouter(router))
if err := b.Run(); err != nil {
log.Fatal(err)
}
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
Grant the bot the required permission in the test guild and remember that Discord's application-command visibility and channel overwrites still apply.
Creating/Configuration
Build a permission set with permissions.Build(permissions.ViewAuditLog, permissions.SendMessages) or permissions.NewBuilder(...).Add(...).Build(). Pass it to RequirePermissions, RequireAnyPermissions, or RequireBotPermissions.
Use RequireRole and RequireAnyRole with snowflake.ID values, and RequireOwners with an application-owned allowlist. OwnerOnly checks the current guild owner. GuildOnly rejects DMs. Validate turns a returned error into an ephemeral response. Prefix permission checks need WithCache and a cache implementing MemberCache.
Using
Basic: all required permissions
RequirePermissions(perms) calls HasAll, so a member must contain every bit. Use RequireAnyPermissions when any one of several bits is sufficient.
Intermediate: layered policy
A moderation command often uses GuildOnly, member permission checks, bot permission checks, RequireRole, and Cooldown. The first rejecting middleware stops the chain.
Advanced: prefix authorization and custom checks
Use PrefixGuildOnly and RequirePrefixPermissions for text commands. Use Validate(func(*InteractionContext) error) for resource-specific checks such as "the target is below the moderator"; return a user-safe message.
Common Patterns
- Check both invoker and bot permissions for actions the bot must perform.
- Use
Administratorsparingly; exact permissions communicate intent better. - Put global logging or tracing middleware on
Router.Useand authorization on the command that needs it. - Keep owner IDs in configuration, not source code, when deployments vary.
- Use
Permission.Hasfor any-bit checks andHasAllfor all-bit checks.
Best Practices
Check the bot separately
Why: a user can be authorized while the bot lacks the channel permission.
Pros: failures are explained before a REST request and avoid predictable 403s.
Cons: AppPermissions is interaction-provided and cannot replace handling permission changes that happen after invocation.
Prefer least privilege
Why: broad permissions increase blast radius.
Pros: safer installations and clearer audits.
Cons: more middleware and REST error paths must be handled when guild policy changes.
Treat prefix cache checks as fallible
Why: a missing member cache entry is not proof that a user lacks permission.
Pros: rejecting on cache miss avoids accidental authorization.
Cons: a cache miss can deny a valid user; fetch the member explicitly when the operation justifies the network request.
Common Mistakes
Incorrect: using Has when all permissions are required.
if perms.Has(permissions.BanMembers | permissions.KickMembers) {
// This allows only one of the two bits.
}2
3
Correct:
if perms.HasAll(permissions.BanMembers | permissions.KickMembers) {
// Both bits are present.
}2
3
Incorrect: checking only the user's permission for a ban command.
router.Command("ban", "Ban a member", handleBan).
RequirePermissions(permissions.BanMembers)2
Correct: check the bot too.
router.Command("ban", "Ban a member", handleBan).
RequirePermissions(permissions.BanMembers).
RequireBotPermissions(permissions.BanMembers)2
3
API Walkthrough
permissions.Permissionis auint64bitfield. Constants includeAdministrator, channel send/read/manage flags, moderation flags, voice flags, thread flags, poll flags, and application-command flags.Permission.AddandRemovemutate a bitfield;Haschecks any matching bit andHasAllchecks every requested bit.permissions.Build(...Permission) Permissioncombines flags.NewBuilder(initial ...Permission) *Builder,Builder.Add,Remove, andBuildprovide fluent composition.permissions.OverwritecontainsID,Type,Allow, andDeny(allPermissiontyped).channels.Overwritealso usespermissions.PermissionforAllowandDeny, so the two types are compatible without manual string conversion.permissions.Calculate(memberID, guildID, guildOwnerID, baseRolePermissions, memberRoleIDs, memberRolePermissions, overwrites) Permissionapplies owner, administrator, everyone, role, and member overwrite precedence.RequirePermissions,RequireAnyPermissions,RequireBotPermissions,RequireRole,RequireAnyRole,RequireOwners,GuildOnly,OwnerOnly, andValidatereturn slash-commandMiddleware.PrefixGuildOnly,RequirePrefixPermissions,PrefixCooldown, and the prefix command configuration methods operate on prefix handlers.Cooldown(time.Duration)returns per-user interaction middleware. The cooldown key includes user, guild, and command name.InteractionContext.MemberPermissions()andBotPermissions()expose the permissions used by custom policy code.
Permission Cheat Sheet
All flags live in permissions/constants.go as 1 << n bitfields. The most common ones:
| Constant | Bit | Decimal | Meaning |
|---|---|---|---|
CreateInstantInvite | 0 | 1 | Create invites |
KickMembers | 1 | 2 | Kick |
BanMembers | 2 | 4 | Ban |
Administrator | 3 | 8 | All permissions, ignores overwrites |
ManageChannels | 4 | 16 | Create/edit/delete channels |
ManageGuild | 5 | 32 | Server settings |
AddReactions | 6 | 64 | React to messages |
ViewAuditLog | 7 | 128 | Read audit log |
ViewChannel | 10 | 1024 | See the channel |
SendMessages | 11 | 2048 | Send messages |
ManageMessages | 13 | 8192 | Delete/pin others' messages |
EmbedLinks | 14 | 16384 | Embeds render |
AttachFiles | 15 | 32768 | Upload files |
ReadMessageHistory | 16 | 65536 | Read older messages |
MentionEveryone | 17 | 131072 | @everyone / @here |
Connect | 20 | 1048576 | Join voice |
Speak | 21 | 2097152 | Talk in voice |
ManageRoles | 28 | 268435456 | Edit roles (below own top role) |
ManageWebhooks | 29 | 536870912 | Manage webhooks |
ModerateMembers | 40 | 1099511627776 | Timeout members |
Full list: permissions/constants.go. Compose flags with permissions.Build(permissions.SendMessages, permissions.ManageMessages) and test with perms.Has(one) / perms.HasAll(several).
How effective permissions resolve (matching Discord's documented algorithm, implemented in permissions.Calculate and bot.resolveMemberPermissions):
- Guild owner: all permissions.
- Base:
@everyonerole permissions OR'd with the member's roles. - If base includes
Administrator: all permissions, channel overwrites are ignored. - Channel role overwrites: deny then allow for the roles the member has.
- Member-specific overwrites: applied last, over role overwrites.
MessageContext.MemberPermissions() and Bot.MemberChannelPermissions* run this pipeline from cached data; InteractionContext.MemberPermissions() reads the value Discord includes in interaction payloads.
Examples
Related APIs
commands.mdfor middleware attachment.caching.mdfor prefix member lookups.../low-level/permissions/README.mdfor bitfields.