Skip to main content
Permissions on Cloak follow a simple rule: a bot declares the capabilities it wants, and a human approves a subset per server when adding it. Declaring is a request, not a grant. There are 31 permissions, with wire codes 0 through 30. You declare and query them by name. The complete list lives in the permissions reference.

Declare what your bot needs

Pass the permissions your bot needs to the Client constructor. The SDK sends this list to the server when your bot logs in.
This is your bot’s manifest. It tells servers what the bot is asking for. It does not give the bot anything on its own.
The manifest is best-effort and never blocks login. If the server refuses it, the SDK routes the failure to the error event and carries on, so a bot can be online with no manifest registered and no exception thrown. Register an error listener if you want to see it.One refusal is worth naming: the server validates every code before it writes anything, and answers on the first code it does not recognise without writing any of them. A single unknown code discards the whole manifest, message_send included. That is reachable when the SDK knows a permission a given backend does not.
A bot with no requiredPermissions at all sends no manifest. That is fine for a bot that only reads, and it is what the quickstart bot does.

A human approves the grant

When someone adds your bot to a server through Server Settings > Invites > Add a Bot, Cloak shows them a consent screen. They approve some or all of what the bot declared. The server then resolves the actual grant for that server and pushes it back to your bot. The result is that the same bot can hold different permissions in different servers, depending on what each server’s admin approved.

Receive the resolved grant

The server sends your bot its resolved permissions per server. The SDK caches the result and surfaces it three ways.

Listen for updates

The permissionsUpdate event fires whenever a server resolves or changes your bot’s grant. It is a live push, not a login-only event: a permission change mid-session arrives here.
effectiveRank is the bot’s effective rank in that server, carried alongside the named booleans on the same PermissionSet.

Check before you act

can() is an advisory check. Use it to avoid firing an action the server would only reject.
can(permission, serverId) takes a non-nullable string, and Message.serverId is string | null. Guard on msg.isDM or on msg.serverId first. Without the guard the example does not compile under strict TypeScript, and at runtime it would return false for every DM, so the bot would go quiet there with no error.

Read the full set

permissions() returns the whole capability set for a server, or undefined if no grant has arrived yet.

When the grant is populated

login() awaits the permission verdicts for every server the bot is already in before it emits ready. So can() and permissions() are already populated inside a ready handler, and you do not need to wait a round trip. undefined from permissions() is for a server the bot joined later, before its verdict landed.
With no verdict, can() returns false and visibleChannelIds() returns an empty array. Absence reads as denied, not as unknown. A false therefore means either “denied” or “no verdict yet”, and your code usually wants to treat both the same way.

Advisory means the server decides

can(), permissions(), and visibleChannelIds() reflect the last grant the server sent. They exist so your bot can pre-gate its own commands, which keeps it from firing actions that will bounce.
These checks are advisory. The server re-checks every action and remains the authority. Never treat a passing can() as a guarantee, and never rely on it for security decisions. It is a courtesy to your own code, not an enforcement point.
The runtime counterparts are where a real denial shows up:
  • Awaited actions (react(), delete(), kick(), ban(), and the rest) reject with a CloakActionError. Its permission field names the permission the bot lacked, when the code maps to one.
  • Sends are fire-and-forget, so a denied send cannot reject. It arrives on the sendRejected event, which also carries a permission field.

Administrator implies everything

The administrator permission is special. If a bot holds it in a server, can() returns true for every other permission in that server.

voice_listen is the one to read twice

One permission does not behave like the rest, and its failure mode is silence. Joining voice and hearing voice are separate grants. voice_connect (plus channel-level view) gets the bot into a voice channel. voice_listen decides whether it is sent any media, and whether it is given a key that could decrypt any. Without voice_listen:
  • The bot joins normally. joinVoice() succeeds.
  • The bot publishes normally. A music or TTS bot works perfectly, and that is the intended configuration, not a degraded one.
  • The bot receives nothing, and holds only its own one-way frame key, so it is cryptographically unable to decrypt any member.
  • Nothing errors. Silence is the only symptom.
selfDeafened is not the answer to “can I hear”. It mirrors what the SDK put in the join frame and says nothing about the grant. To ask the real question, use client.can('voice_listen', serverId) (advisory, and remember false also covers “no verdict yet”), or read the bot’s own entry in voiceRoster(channelId). Grant voice_listen deliberately in the other direction too: a bot that holds it is given the room key and can decrypt everyone in the channel. That is what the permission means. See Voice keys and scopes.

Permission names

You declare and query permissions by name: message_send, member_ban, manage_channels, administrator, and 27 more. The names are stable, and the integer codes they map to are the wire contract. PERMISSION_CODES (name to code), PERMISSION_NAMES (code-indexed), and decodePermArray() are exported if you need to work with the codes directly. The full table, all 31 rows, is in the permissions reference.

Next

Permissions reference

All 31 names and codes in one table.

Message delivery

Permissions decide which channels your bot can view.

Errors

CloakActionError, sendRejected, and the error event.

Voice keys and scopes

What voice_listen actually changes.