> For the complete documentation index, see [llms.txt](https://mango-5m.gitbook.io/mango5m-docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://mango-5m.gitbook.io/mango5m-docs/resources/mango-admin/configuration/discord-integration.md).

# Discord Integration

Staff roles managed in Discord, and calling a player to a voice channel.

## Staff roles from Discord

Most servers already decide who is staff in Discord. Turn this on and they are staff in the panel too: hold the Discord role, hold the panel role — no identifier to add, and nothing to remember when somebody is promoted.

{% stepper %}
{% step %}

### Give it a bot

The bot must be **in** each guild you map. It needs no privileged intent.

{% code title="shared/config/integrations.lua" %}

```lua
Config.Discord = {
    botToken = 'YOUR_BOT_TOKEN_HERE',
    guildId  = '000000000000000000',
}
```

{% endcode %}
{% endstep %}

{% step %}

### Map the roles

`['<discord role id>'] = '<panel role name>'`. The name is the one on the Roles page, matched without case.

{% code title="shared/config/integrations.lua" %}

```lua
Config.Discord = {
    useDiscordRoles = true,

    roles = {
        ['1111111111111111111'] = 'Admin',
        ['2222222222222222222'] = 'Moderator',
        ['3333333333333333333'] = 'Trial Moderator',

        -- a role in another guild
        ['4444444444444444444'] = { guild = '555555555555555555',
                                    role  = '4444444444444444444',
                                    grants = 'Moderator' },
    },

    readOnJoin = true,
    sync = { enabled = true, refreshMinutes = 10 },
}
```

{% endcode %}
{% endstep %}

{% step %}

### Check the console

It prints what it will do, and — the one that matters — what it cannot:

```
[mango_admin] Discord role 1111111111111111111 in guild 900... grants "Admin"
[mango_admin] Config.Discord.roles points Discord role 222... at "Head of Staff", and there is
              no role by that name - nobody will be granted anything by it
```

{% endstep %}
{% endstepper %}

Roles can also be mapped **inside the panel**, on the Roles page → **Discord** tab. Both work at once, and a mapping written in the config cannot be deleted from in there.

{% hint style="success" %}
**It is not a one-off copy into the member list.** Every permission check works it out from the roles they hold *now*, so taking the Discord role away takes the panel role away with it, within `refreshMinutes`. Nobody is left as staff because somebody forgot to remove them here as well.

`readOnJoin` reads their roles a couple of seconds after they connect, so staff are staff from the moment they are in rather than at the next sweep. There is a **Sync now** button for when you cannot wait at all.
{% endhint %}

`useDiscordRoles = false` stops Discord granting panel roles entirely, while still letting the panel tell you who is in your guild. That is a different switch from `sync.enabled`, which decides whether Discord is read at all.

{% hint style="warning" %}
**A permission check never waits on Discord.** Roles are read on a background thread into a cache, and permissions read the cache. Hanging every click in the panel on somebody else's API — one that rate limits and occasionally goes down — would make the whole thing unusable. Somebody whose roles have not been read yet simply does not hold the Discord-granted ones yet.

Keep yourself in `Config.Owners` so losing Discord cannot lock you out.
{% endhint %}

## Calling a player to voice

`Config.StaffCall` lets staff summon somebody to your support voice channel. Their screen blurs, whatever they were doing stops — including the car they were driving — and a card explains that a staff member is asking for them, with a button that opens the channel.

{% code title="shared/config/reports.lua" %}

```lua
Config.StaffCall = {
    url          = 'https://discord.com/channels/<guild>/<channel>',
    channelId    = '000000000000000000',
    channelName  = 'Support',
    preferDesktop = true,   -- open the Discord client rather than a browser tab
    freeze = true, blur = true,
}
```

{% endcode %}

The **Close** button does not appear until they have pressed **Open Discord**.

{% hint style="info" %}
**Nothing can force a Discord client into a voice channel.** The button opens the channel; joining is still their decision. The panel tells the staff member honestly whether the link was opened, and never claims more than that.
{% endhint %}
