Skip to main content
Bridge.Permissions answers one question: does this player hold this permission node? A node is granted if a server ACE allows it, or if the player has a Discord role you mapped to that node. No config files, no per-script admin lists. The thing to understand up front: you wire it with labels, not raw Discord role IDs scattered through your code. A label like 'admin' is a friendly name. You define what role ID(s) the label means in one place, and what permission node(s) it grants in another. The real Discord role snowflake lives in exactly one call.

The two-step flow

1

Map labels → Discord role IDs

Bridge.Discord.RegisterRoles({ label = 'roleId' }) — tells the bridge that the label admin means Discord role 1234…. A label can map to several role IDs (any of them grants it).
2

Map labels → permission nodes

Bridge.Permissions.RegisterNodes({ label = { 'node', ... } }) — tells the bridge that the same label admin grants the nodes myscript.admin, myscript.manage, etc.
3

Check a node

Bridge.Permissions.Has(src, 'myscript.admin') — the bridge resolves the node back to the label(s) that grant it, then checks whether the player holds that label’s Discord role.
In RegisterNodes, the key (admin) is the label, not a role ID. The actual Discord role ID is defined in RegisterRoles. Mixing these up is the most common mistake — there is no role ID anywhere in your RegisterNodes call.

Step 1 — register your roles

Map each label to its Discord role snowflake. You can pass a table, or a path to a roles.json shipped in your resource. A label may map to one ID or a list.
-- server
Bridge.Discord.RegisterRoles({
    admin = '111111111111111111',                              -- one role ID
    mod   = { '222222222222222222', '333333333333333333' },    -- several — any grants it
    vip   = '444444444444444444',
})
Bridge.Discord.RegisterRoles('roles.json')   -- loads the file from your resource
This is where — and the only place where — your real Discord role IDs live. To re-point a label to a different role later, an owner edits this one map; nothing else changes.

Step 2 — map roles to nodes

Map the same labels to the permission node strings your script checks. A node string is yours to name (myscript.admin, garage.impound, …).
-- server
Bridge.Permissions.RegisterNodes({
    admin = { 'myscript.admin', 'myscript.manage' },   -- the 'admin' LABEL → these nodes
    mod   = 'myscript.kick',
    vip   = { 'myscript.vip' },
})
A node value can be a single string or a list. Wildcards work: registering myscript.* for a label grants myscript.admin, myscript.manage, and any other myscript.* node; * grants everything.

Step 3 — check a node

if Bridge.Permissions.Has(src, 'myscript.admin') then
    -- granted via a server ACE, OR via the 'admin' Discord role
end
Has is the resolution that ties the two maps together:
  1. If a server ACE allows the node (IsPlayerAceAllowed), grant it immediately. ACEs are honored first.
  2. Otherwise, find every label whose nodes include that node (wildcards considered), and check whether the player holds any of those labels’ Discord roles.
  3. Otherwise, deny. Fail-closed.
MethodPurpose
Bridge.Discord.RegisterRoles(map|path)Map labels → Discord role ID(s).
Bridge.Permissions.RegisterNodes(map)Map labels → permission node(s).
Bridge.Permissions.Has(src, node)Resolve a node to its label(s) and check ACE + Discord role.

Discord setup

The Discord side is what makes role-granted nodes work. The bridge talks to the Discord REST API directly with a server-only bot token — there is no separate bot process. It caches each player’s guild profile (roles, avatar, username, …) on join. Enable it with two non-replicated convars. Use set, never setr, so the token never reaches clients:
# server.cfg
set atlas:discord:token "YOUR_BOT_TOKEN"
set atlas:discord:guild "YOUR_GUILD_ID"
Never use setr for the bot token. setr replicates the value to every client, leaking your token. set keeps it server-only — it is used only in an Authorization header server-side.
If you only use ACE permissions (no Discord roles), you can skip the Discord setup entirely — Has still works via IsPlayerAceAllowed.

End-to-end example

A garage script that gates impound on staff, granting it to anyone with the admin or mod Discord role (or a matching ACE).
-- server — once, at resource start
Bridge.Discord.RegisterRoles({
    admin = '111111111111111111',
    mod   = { '222222222222222222', '333333333333333333' },
})

Bridge.Permissions.RegisterNodes({
    admin = { 'garage.impound', 'garage.manage' },
    mod   = 'garage.impound',
})

-- gate a protected net handler on the node
Bridge.Net.On('garage:impound', {
    permission = 'garage.impound',          -- the net layer checks Has() before your handler runs
    payload    = Bridge.Validate.obj({ netId = Bridge.Validate.int() }),
    handle = function(src, data)
        impoundVehicle(data.netId)
    end,
})

-- or check inline anywhere
if Bridge.Permissions.Has(src, 'garage.manage') then
    openManagementMenu(src)
end
A player with the admin Discord role, the mod role, or a server ACE granting garage.impound passes. Everyone else is denied — the handler never runs.
Bridge.Net.On accepts a permission field that calls Has for you before the handler executes — the cleanest place to gate a client→server action. See Networking.

Two real gaps to know about

Bridge.IsAdmin is not this system. Bridge.IsAdmin(src) is a framework admin check (ESX/QB group), entirely separate from these Discord-role nodes. Use Bridge.Permissions.Has for node-based gating; use Bridge.IsAdmin only when you specifically want the framework’s own admin flag.
There is no built-in default admin. The bridge ships with no roles and no nodes registered. Until you call RegisterRoles and RegisterNodes (or grant ACEs in server.cfg), Has denies everything. Register your own roles and nodes during setup.

Client side

Clients can read only their own Discord profile (for NUI avatars/banners/tags) — never role checks, which are server-authoritative.
-- client
Bridge.Discord.GetAvatar()
Bridge.Discord.GetUsername()
Bridge.Discord.GetDisplayName()
Bridge.Discord.GetGuildTag()
Bridge.Discord.GetBanner()