# Building a Discord role whitelist for FiveM

> Whitelist a FiveM server by Discord role: the discord identifier, deferrals in playerConnecting, checking roles over the REST API and caching.

Source: https://fam-tools.com/guides/fivem-discord-whitelist-script · FAM Tools · published 2026-09-11 · updated 2026-09-11 · about 12 min

**Short answer.** In `playerConnecting`, call `deferrals.defer()`, read the player’s `discord` identifier, and ask the Discord API (`GET /guilds/{guild}/members/{user}` with your bot token) whether they are in the guild and hold the whitelist role. Call `deferrals.done()` to let them in or `deferrals.done('reason')` to refuse. Keep the bot token in a server-only convar and cache results briefly.

## Key points

- The Discord identifier only exists if Discord is running and linked to FiveM.
- Always `deferrals.defer()` first and `deferrals.done()` on every path.
- Check roles server-side with a bot token — never from the client.
- Put the token in `set` (server-only), never `setr`.
- Tell refused players exactly what to do next.

Most whitelisted roleplay servers accept players through a Discord application, then give them a role. A small server script can check that role while the player connects and turn everyone else away with a helpful message. It needs a Discord bot, the player’s Discord identifier and connection deferrals.

## Step 1: create the Discord bot

1. In the Discord Developer Portal, create an application and add a bot; copy its token.
2. Invite the bot to your Discord server (it only needs to read members; no admin rights).
3. Enable Developer Mode in Discord and copy your guild ID and the whitelist role ID.

*server.cfg — server-only convars*

```cfg
set wl_botToken "your-bot-token"
set wl_guildId "123456789012345678"
set wl_roleId "234567890123456789"
```

## Step 2: the connection check

*server.lua*

```lua
local TOKEN = GetConvar('wl_botToken', '')
local GUILD = GetConvar('wl_guildId', '')
local ROLE = GetConvar('wl_roleId', '')
local JOIN = 'https://discord.gg/yourinvite'

local function hasRole(discordId)
    local p = promise.new()
    PerformHttpRequest(('https://discord.com/api/v10/guilds/%s/members/%s'):format(GUILD, discordId), function(status, body)
        if status ~= 200 then return p:resolve(status == 404 and 'notmember' or 'error') end
        local member = json.decode(body)
        for _, r in ipairs(member.roles or {}) do
            if r == ROLE then return p:resolve('ok') end
        end
        p:resolve('norole')
    end, 'GET', '', { Authorization = 'Bot ' .. TOKEN })
    return Citizen.Await(p)
end

AddEventHandler('playerConnecting', function(name, setKickReason, deferrals)
    local src = source
    deferrals.defer()
    Wait(0)
    deferrals.update('Checking your Discord whitelist…')

    local id = GetPlayerIdentifierByType(src, 'discord')
    if not id then
        return deferrals.done('Open Discord and restart FiveM so we can see your account. Join: ' .. JOIN)
    end

    local result = hasRole((id:gsub('discord:', '')))
    if result == 'ok' then
        deferrals.done()
    elseif result == 'notmember' then
        deferrals.done('You are not in our Discord yet. Join and apply: ' .. JOIN)
    elseif result == 'norole' then
        deferrals.done('You are not whitelisted yet. Apply in our Discord: ' .. JOIN)
    else
        deferrals.done('Whitelist check failed, please try again in a minute.')
    end
end)
```

> **Do not skip deferrals.done** — A deferral that never finishes leaves the player stuck on “Connecting”. Every branch — including API errors — must call `deferrals.done`.

## Caching and rate limits

Discord rate-limits bots. Cache positive results for a few minutes per Discord ID so reconnect loops do not hammer the API, and decide what happens during a Discord outage (refuse with a clear message, or allow players you saw recently).

## Make refusals helpful

- Say exactly what is missing: Discord not linked, not in the guild, or no role.
- Include the invite link and where to apply.
- For a richer screen with buttons and links, show an adaptive card with `deferrals.presentCard` instead of plain text.

## Ready-made options

Many frameworks and txAdmin include whitelist features (txAdmin has its own approval-based whitelist), and there are established Discord permission resources. Writing your own is worth it when you want custom messages or several roles; otherwise a maintained resource saves time.

## Questions

### How do I whitelist my FiveM server with Discord?

Check the player’s Discord roles during `playerConnecting` with a bot and the Discord API, and refuse the connection with `deferrals.done(reason)` if the role is missing.

### Why does the whitelist say my Discord is not linked?

FiveM only sees your Discord ID when the Discord desktop app is running before FiveM starts and FiveM is authorised.

### Is it safe to put my bot token in server.cfg?

Yes, with `set` (server-only). Never use `setr`, which sends the value to clients.

### Why are players stuck on Connecting?

A code path never calls `deferrals.done`. Make sure every branch finishes the deferral.

### Does txAdmin have a whitelist?

Yes — txAdmin includes a whitelist mode with approvals, independent of Discord roles.

## Related

- https://fam-tools.com/guides/fivem-ace-permissions
- https://fam-tools.com/guides/fivem-player-identifiers
- https://fam-tools.com/guides/txadmin-admins-permissions
- https://fam-tools.com/guides/fivem-ban-system
