PlayDeck
Home / Course / Config & Permissions
Track A · Module 7

Config & Permissions

Most "my script is broken" messages come down to two things: a misplaced comma in a config table, or a permission check that lives on the client where any cheater can delete it. This lesson fixes both. You'll learn to write config tables that don't bite, gate features with ACE permissions, and put the real security check where it counts — the server.

Config tables: the dial panel, not the engine

A config table is a plain Lua table you keep in its own file, separate from your logic. The point is leverage: a server owner who runs your script should be able to change how much money a job pays or where a garage spawns without ever opening the code that does the work. You change dials, not wiring.

The pattern is always the same — declare the table, then hang values off it:

-- config.lua
Config = {}

Config.MaxStored = 3                 -- a number: no quotes
Config.JobName  = "mechanic"         -- a string: quotes required
Config.AdminAce = "playdeck.garage.admin"  -- the ACE we check later

Config.Locations = {                 -- a list of tables (array-style)
    { label = "Legion", coords = vec3(215.0, -810.0, 30.7) },
    { label = "Sandy",  coords = vec3(1834.0, 3686.0, 34.2) },
}

Config is global so other files in the same resource can read it. Nothing here talks to the game yet — it's just data. That separation is the whole reason config files exist.

The comma is load-bearing

Here is the single most common syntax error in FiveM scripting, and it has nothing to do with FiveM: every entry in a Lua table is separated by a comma. Lua tables aren't whitespace-aware like Python or YAML. Put two entries on two lines with no comma between them and Lua doesn't see "two items" — it sees a broken expression and your whole resource fails to start.

Config.Locations = {
    { label = "Legion", coords = vec3(215.0, -810.0, 30.7) }   -- no comma...
    { label = "Sandy",  coords = vec3(1834.0, 3686.0, 34.2) }  -- ...syntax error
}

Two things that surprise beginners, both true and both useful:

When a resource won't start, read the server console. Lua tells you the file and line of a syntax error. The line it names is usually the one after the missing comma — so check the line above the one it points at first.

ACE permissions and principals

Now, who is allowed to use the admin features? FiveM's built-in answer is the ACE system — Access Control Entries. Two words to anchor:

You wire them together in server.cfg:

# server.cfg — define WHO can do WHAT
add_ace   group.admin            playdeck.garage.admin  allow   # the role gets the permission
add_principal identifier.fivem:1234567  group.admin               # this player IS an admin

Read top-down: the admin group is allowed the garage permission, and player 1234567 is in the admin group. Change staff by editing principals — you never touch the script. That's the same leverage idea as config, applied to people.

You don't have to memorize the exact directive names. Keep the official Cfx ACL/principals page open and link to it from your script's README — the syntax is stable but it's the kind of thing worth confirming against the real reference, not from memory.

Put the check on the server (the part AI gets wrong)

This is the lesson inside the lesson. When you ask an AI to "add an admin menu," it will very often check the permission on the client — and a client check protects nothing. The client is the player's own machine. A cheater can edit it, skip it, or fake the answer. If the only gate is client-side, you don't have a gate.

The fix: the client may ask, but the server decides. With ox_lib, you do this through lib.callback — a typed request/response between client and server. The server runs IsPlayerAceAllowed, and only sends data back if the check passes.

-- server.lua
lib.callback.register("playdeck:garage:openAdmin", function(source)
    -- `source` is the player's server id, set by the server itself — the client can't spoof it.
    if not IsPlayerAceAllowed(source, Config.AdminAce) then
        return false  -- deny: no menu, and no garage data ever leaves the server
    end
    return { garages = GetAllGarages() }  -- replace with your real data source
end)
-- client.lua
local data = lib.callback.await("playdeck:garage:openAdmin", false)
if not data then return end  -- denied: there is simply nothing to open
-- safe to build the menu now; the sensitive data only exists because the server allowed it

Notice the menu can't even render for an unauthorized player, because the data they'd need never arrives. That's the difference between hiding a button and actually securing a feature.

Two habits that keep AI from steering you wrong here: pin one framework (decide Qbox/QBCore or ESX up front — IsPlayerAceAllowed is a real native, but AI loves to invent fake exports and blend frameworks), and verify every native against the real reference and the PlayDeck sandbox before trusting it. If you can't find a native in the official docs, it doesn't exist.

Practice

In the PlayDeck browser sandbox — no FiveM needed — paste this and run it. We've mocked the permission check so you can feel the logic:

local Config = { AdminAce = "playdeck.garage.admin" }
local allowed = { ["alice"] = true, ["bob"] = false }  -- pretend ACL

-- stand-in for the real server native
local function IsPlayerAceAllowed(player, ace)
    return allowed[player] == true
end

local function openAdmin(player)
    if not IsPlayerAceAllowed(player, Config.AdminAce) then
        return "DENIED"
    end
    return "OK — sending garage data"
end

print("alice:", openAdmin("alice"))  -- OK
print("bob:  ", openAdmin("bob"))    -- DENIED

Now break it on purpose: delete a comma inside the allowed table and watch the sandbox report the syntax error and the line. Then add a third player, "carol", set them to true, and confirm they pass. You just exercised both halves of this lesson — comma discipline and a server-side gate — without a server.

Recap

Learn it by building it

PlayDeck is an original course on building GTA roleplay scripts with AI — Lua, frameworks, NUI, debugging, and a browser sandbox to test every lesson without booting the game.

Browse the course