Skip to main content

Permission System

Job + Discord role gating with built-in Discord bot fetcher — used everywhere in Carplay V2.

This section

Permission System

Almost every gated feature in Carplay V2 (apps, modules, autopilot, drift mode, autopark, security cam, dashcam, screen install) accepts the same pair of arrays:

lua
{
jobs = {}, -- e.g. {"police", "ambulance"}
discordRoles = {}, -- e.g. {"123456789012345678"}
}

This page explains exactly how that gating works.

Logic

jobsdiscordRolesResult
EmptyEmptyEveryone allowed (no restrictions)
SetEmptyPlayer must have a matching job
EmptySetPlayer must have a matching Discord role
SetSetJob match OR Discord role match — either suffices

The OR logic in the both-set case is intentional: players with the right job get access regardless of Discord, and Discord-only members (donors, VIPs) get access regardless of in-game job.

Job Matching

Job is read from the player's framework data:

FrameworkSource
ESXxPlayer.getJob().name
QBCorePlayer.PlayerData.job.name
QBoxPlayer.PlayerData.job.name
Standalonenil — only discordRoles works on standalone

Match is case-sensitive exact"police" matches only "police", not "Police" or "PoliceOfficer".

Discord Role Matching

Discord roles are fetched via a built-in bot fetcher. You don't need any external Discord script — Carplay V2 talks directly to the Discord API.

Setup

In server/config.lua:

lua
ServerConfig.DiscordBotToken = "YOUR_BOT_TOKEN"
ServerConfig.DiscordGuildId = "YOUR_GUILD_ID"
ServerConfig.DiscordCacheDuration = 120 -- seconds

If either is empty → all discordRoles checks are silently skipped.

How It Works

  1. Player joins or first-uses a permission-gated feature
  2. The script extracts their Discord ID from FiveM identifiers (discord:123...)
  3. It calls https://discord.com/api/v10/guilds/{GUILD}/members/{DISCORD_ID} with the bot token
  4. The returned roles array is cached for DiscordCacheDuration seconds
  5. On any future check within that window, the cache is used (no API call)
  6. When the player disconnects, their cache entry is cleared

Bot Requirements

  • Bot must be in your Discord server
  • Bot must have Server Members Intent enabled (Discord Developer Portal → Bot tab → Privileged Gateway Intents)
  • Bot needs at minimum View Server Members permission

Getting a Role ID

  1. Enable Discord Developer Mode: User Settings → App Settings → Advanced → Developer Mode
  2. Server Settings → Roles → right-click any role → Copy Role ID

Cache Tuning

The default cache is 120 seconds (2 min). For very busy servers you might want to bump this to 300-600s to reduce API calls; for servers where roles change frequently, lower to 30s for faster propagation.

lua
ServerConfig.DiscordCacheDuration = 300 -- 5 min

Set 0 to disable caching entirely (one API call per check — not recommended).

Where Permissions Apply

FeatureConfig pathNotes
Each appConfig.Apps[i].jobs / discordRolesPer-app gating
AutopilotConfig.AutoPilotPermissions
Drift ModeConfig.DriftMode
Auto ParkConfig.AutoPark
Security CameraConfig.SecurityCamera
DashCamConfig.DashCam
In-Vehicle ScreenConfig.ScreenRestricts who can install screens

Modules without permission arrays (e.g. Config.Modules.MusicPlayer) are simply on/off toggles open to everyone.

Server-Side Helper

If you want to call the permission check yourself in custom code, the helper is exported globally on the server:

lua
-- in any server-side script that runs after code9_carplayv2 starts
local jobs = {"police"}
local roles = {"123456789012345678"}
local playerJob = GetPlayerJobServer(source) -- helper from open.lua
local playerDiscordRoles = GetPlayerDiscordRoles(source) -- cached fetch
local allowed = CheckPermission(jobs, roles, playerJob, playerDiscordRoles)

Example Configurations

"Police-only autopilot"

lua
Config.AutoPilotPermissions = {
jobs = {"police"},
discordRoles = {},
}

"Donors get drift mode"

lua
Config.DriftMode = {
enabled = true,
jobs = {},
discordRoles = {"123456789012345678"}, -- "Donor" role ID
}

"Mechanics OR donors can install screens"

lua
Config.Screen.jobs = {"mechanic"}
Config.Screen.discordRoles = {"123456789012345678"} -- "Donor" role ID

"Hide Notes app from non-staff"

lua
-- in Config.Apps, find the notes entry:
{id = "notes", icon = "./apps/note.png", enabled = true, jobs = {"admin", "moderator"}, discordRoles = {}}