Permissions
Permissions
There are three admin scopes and two player-facing gates.
The three admin scopes
| Scope | Opens | Default groups | Default ACE |
|---|---|---|---|
lab |
Balance Lab, workbench management, showcase | god, admin |
gunsmith.balance |
mod |
The skin moderation queue | god, admin, mod |
gunsmith.mod |
range |
The Shooting Range | god, admin, mod |
gunsmith.range |
Config.Access = {
lab = { groups = { 'god', 'admin' }, ace = 'gunsmith.balance' },
mod = { groups = { 'god', 'admin', 'mod' }, ace = 'gunsmith.mod' },
range = { groups = { 'god', 'admin', 'mod' }, ace = 'gunsmith.range',
zones = { 'shooting_range' } },
}
Two layers, either one grants
Each scope is checked twice, and either check passing is enough:
groups— your framework's own permission system.- QBCore / Qbox:
QBCore.Functions.HasPermission(src, groups), granted with/setperm - ESX:
xPlayer.getGroup()must be in the list, granted through theusers.groupcolumn
- QBCore / Qbox:
ace— the standalone path and the fallback.add_ace group.admin gunsmith.balance allow
When a framework is present, groups is asked first. If it says no, ace is still tried. So a server can grant access either way, and mixing the two is fine.
When a grant does not work
/gunsmith:whoami
This is deliberately not permission-gated — the whole point is to answer "why don't I have permission". It prints, for every scope, which groups were tested, which ACE object was tested, the result of each, and which path granted access if any did.
The two usual answers:
The grant went to the framework, and the framework's own check is failing. Add the ACE as well — either path is enough.
The ACE was added to a principal the player is not in.
add_ace group.adminonly helps a player who is actually ingroup.admin. Grant the licence directly to test:add_ace identifier.license:YOUR_LICENSE_HERE gunsmith.balance allow
Player-facing gates
Reaching the workshop
Config.Access.workshop = {
anywhere = false,
zones = {},
target = true,
command = false,
commandName = 'gunsmith',
}
| Field | Meaning |
|---|---|
anywhere |
Skip all location checks. Off by default |
zones |
Fixed workshop points, naming entries in Config.Zones. Empty by default |
target |
Use ox_target / qb-target for the interaction; falls back to a marker |
command |
Enable /gunsmith from anywhere. Off by default |
Turn them on only if that is the server you want. command = true is still subject to the rest of workshop; "on" does not mean "open to everyone".
Reaching the Skin Studio
Config.Access.studio = { requireItem = nil, jobs = nil }
| Field | Meaning |
|---|---|
requireItem |
An inventory item the player must hold, e.g. 'design_kit'. nil = no item |
jobs |
Restrict the studio to certain jobs. nil = everyone |
Per-workbench access
Beyond all of the above, every individual workbench carries its own rule — public, a specific citizen, a job, or a job plus a minimum grade. That rule is always enforced on the server. See Workbenches.
Hooks
Server-side, in server/config.lua, never sent to clients:
| Hook | Signature | Use |
|---|---|---|
canCustomize |
(src, serial, category) |
Veto an individual edit |
priceFor |
(src, serial, changes) |
Return the price of an edit |
onPayment |
(src, amount) |
Take the money; return false to refuse the edit |
canModerate |
(src) |
Who may review skins |
skinValidator |
(def) |
Reject a design — banned imagery, for example |
These are the cut points for attaching your own economy and rules without touching the script.