# Permissions

Check the caller's permissions, your bot's permissions, or the caller's role before a handler runs. Covers channel and server permissions, event handlers, and rewording each refusal.

A kick command can fail two ways. The caller might not be allowed to kick, or your bot might not be. Checked by hand, the first is a permissions lookup at the top of every handler. For the second, Discord returns a Missing Permissions error after your handler has already replied or started work.

[`RequirePermissions`](https://docs.seedcord.org/packages/core/latest/require-permissions) checks the caller and [`RequireBotPermissions`](https://docs.seedcord.org/packages/core/latest/require-bot-permissions) checks your bot, both before `execute()` runs. [`RequireRole`](https://docs.seedcord.org/packages/core/latest/require-role) checks one role by id.

```ts title="src/handlers/Kick.ts"
import {
    Gated,
    RequireBotPermissions,
    RequirePermissions,
    SlashHandler,
    SlashRoute
} from '@seedcord/gateway';
import { PermissionFlagsBits } from 'discord.js';

@Gated(
    RequirePermissions([PermissionFlagsBits.KickMembers]),
    RequireBotPermissions([PermissionFlagsBits.KickMembers])
)
@SlashRoute('kick')
export class Kick extends SlashHandler<'kick'> {
    public async execute(): Promise<void> {
        const target = this.options.getUser('target');

        await this.reply(`Kicked ${target.username}.`);
    }
}
```

The two permission gates take a [`PermissionScope`](https://docs.seedcord.org/packages/core/latest/permission-scope), an array of flag bits. A scope with several bits requires all of them.

```ts
RequirePermissions([
    PermissionFlagsBits.ManageMessages,
    PermissionFlagsBits.ManageThreads
]);
```

An http bot doesn't depend on discord.js, so the same bits come from `discord-api-types/v10` there. The values are identical.

## Administrator passes everything

Discord grants every permission to **Administrator**, so both permission gates apply that rule. A caller holding **Administrator** passes any scope you name.

> **Warning**
>
> You hold **Administrator** on your own test server, as the owner or through a role. Both permission gates pass there, so you cannot tell a broken one from a working one. Test with a second account that doesn't have an admin role.

`RequireRole` reads role ids, which is why it also refuses an administrator who lacks the role.

## Channel permissions and server permissions

The `in` option picks which set the gate reads.

```ts
RequirePermissions([PermissionFlagsBits.BanMembers], { in: '
```

{/* prettier-ignore-start */}

| `in`                     | reads                                                       | holds true when                            |
| ------------------------ | ----------------------------------------------------------- | ------------------------------------------ |
| `'channel'`, the default | the effective set for the invoked channel, after overwrites | Discord would allow the action right here  |
| `'guild'`                | the base set from roles alone                               | the roles grant it somewhere in the server |

{/* prettier-ignore-end */}

A channel overwrite can remove a permission that a role granted. `'channel'` includes that overwrite in what it reads, so it answers "can they do this here". `'guild'` answers "do their roles grant this anywhere in the server", which is what you want when the command acts on the whole server.

```ts
RequirePermissions([PermissionFlagsBits.BanMembers], {
    in: 'guild'
});
```

> **Gateway only**
>
> `in: 'guild'` reads role-derived sets that only the gateway cache carries. So the gate requires a [`GuildPermissionsContext`](https://docs.seedcord.org/packages/core/latest/guild-permissions-context), which the http transport never provides.

Putting a guild-scoped gate on an http handler fails at the decorator line.

```ts
@Gated(
    RequirePermissions([PermissionFlagsBits.BanMembers], {
        in: 'guild'
    })
)
@SlashRoute('ban')
export class Ban extends SlashHandler<'ban'> {
    public async execute(): Promise<void> {}
}
```

## On a gateway event handler

An interaction payload carries both channel sets. A gateway event doesn't carry either one, which changes what `'channel'` does on each gate.

{/* prettier-ignore-start */}

| on an event, `in: 'channel'` | what happens                                                                                                      |
| ---------------------------- | ----------------------------------------------------------------------------------------------------------------- |
| `RequirePermissions`         | reads the caller's role-derived set, the same value `'guild'` reads. Channel overwrites go uncounted              |
| `RequireBotPermissions`      | reads `app_permissions`, which an event never carries, so it refuses every time and reports the set as unresolved |

{/* prettier-ignore-end */}

Use `in: 'guild'` on an event handler. On the caller side it reads the same set `'channel'` would there, and on the bot side it's the only scope that can pass.

```ts
RequireBotPermissions([PermissionFlagsBits.ManageMessages], {
    in: 'guild'
});
```

## Requiring a role

[`RequireRole`](https://docs.seedcord.org/packages/core/latest/require-role) takes one role id and reads the caller's roles. Roles are server-wide, so it does not take a scope option.

```ts
@Gated(RequireRole('221439658686939136'))
@SlashRoute('roles')
export class Roles extends SlashHandler<'roles'> {
    public async execute(): Promise<void> {
        await this.reply('Here they are.');
    }
}
```

Combine several with [`or`](https://docs.seedcord.org/packages/core/latest/or) when holding any one of the roles is enough.

## Wording each refusal

Each of these gates refuses for two different reasons. Its options carry one slot per reason, and each slot takes the `message` and `notice` pair from [Gates](/checks/gates).

{/* prettier-ignore-start */}

| gate                    | slot          | refuses when                                                      |
| ----------------------- | ------------- | ----------------------------------------------------------------- |
| `RequirePermissions`    | `notInGuild`  | the set is unavailable, outside a server or unresolved inside one |
| `RequirePermissions`    | `missing`     | a bit in the scope is absent                                      |
| `RequireBotPermissions` | `notInGuild`  | the same, for your bot's set                                      |
| `RequireBotPermissions` | `missing`     | the same, for your bot's bits                                     |
| `RequireRole`           | `notInGuild`  | the call came from outside a server                               |
| `RequireRole`           | `missingRole` | the caller lacks the role                                         |

{/* prettier-ignore-end */}

```ts
RequirePermissions([PermissionFlagsBits.BanMembers], {
    notInGuild: { message: 'Run this in a server.' },
    missing: { message: 'You cannot ban here.' }
});

RequireRole('221439658686939136', {
    missingRole: { message: 'Subscribers only.' }
});
```

Leaving a slot out keeps the default card. The permission gates list every missing bit on it by name, and `RequireRole` mentions the role.

A check these gates don't cover, like whether the caller linked an account, is [a gate you write yourself](/checks/your-own).
