Check an allowlist before a player joins
Use the server’s playerConnecting event when a decision must happen before a player enters the session. Call deferrals.defer(), yield a tick, perform the check, and finish with deferrals.done(). A rejection should explain what the player can do next, without exposing identifiers or internal errors.
This framework-independent example uses a local allowlist. It needs a working development server, not QBCore, ESX, an external API, or a database. Do not run it alongside another connection queue until you have defined which resource owns admission.
Create the resource
Create resources/[local]/doc_allowlist/ with these three files. The JSON file is server configuration: do not add it to the manifest’s files list, a shared script, or a client script.
fxmanifest.lua
fx_version 'cerulean'
game 'gta5'
server_script 'server.lua'allowlist.json
Replace the example key with a real license: identifier obtained through your trusted server administration tools. The placeholder deliberately admits nobody.
{
"license:REPLACE_WITH_YOUR_LICENSE": true
}server.lua
local allowlist
local raw = LoadResourceFile(GetCurrentResourceName(), 'allowlist.json')
-- A missing, oversized, or malformed configuration fails closed.
if raw and #raw <= 1024 * 1024 then
local ok, decoded = pcall(json.decode, raw)
if ok and type(decoded) == 'table' then
local valid = true
for identifier, enabled in pairs(decoded) do
if type(identifier) ~= 'string'
or not identifier:match('^license:')
or type(enabled) ~= 'boolean' then
valid = false
break
end
end
if valid then allowlist = decoded end
end
end
if not allowlist then
print('[doc_allowlist] Invalid allowlist.json; connections will be refused.')
end
AddEventHandler('playerConnecting', function(_, _, deferrals)
local playerId = source -- Save before yielding; this is a temporary ID.
deferrals.defer()
Wait(0)
deferrals.update('Checking access to this server...')
local rejection
if not allowlist then
rejection = 'Admission is temporarily unavailable. Please contact staff.'
else
local license
for _, identifier in ipairs(GetPlayerIdentifiers(playerId)) do
if identifier:sub(1, 8) == 'license:' then
license = identifier
break
end
end
if not license then
rejection = 'Your game license could not be verified. Reconnect and try again.'
elseif allowlist[license] ~= true then
rejection = 'This server requires approval. Please contact the server staff.'
end
end
Wait(0) -- A tick must separate update() from done().
deferrals.done(rejection) -- nil admits; a string rejects. Exactly one call.
end)In the server console, run refresh, then ensure doc_allowlist. Reconnect your test client. An allowed account proceeds; an unlisted account receives the refusal message. Changes to this example’s JSON take effect after restart doc_allowlist, because the resource reads configuration at startup.
Verify failure paths, not just a successful join
| Test | Expected result |
|---|---|
Correct identifier mapped to true | Connection continues |
Identifier missing, or mapped to false | Approval message, no admission |
| Invalid JSON or missing file | Generic temporary-unavailability message; server log explains configuration failure |
| Resource starts with an empty object | Nobody admitted; not an accidental open server |
| Two clients connect together | Each check uses its own captured playerId |
A connecting player is not a fully spawned character. Do not require a ped, coordinates, inventory, or a framework player object here. See network and local IDs for the difference between identifiers and session IDs.
Extending this to a database or HTTP service
The example intentionally has no external wait. When adding one, define a timeout, handle unavailable services, and guarantee that every path finishes admission at most once. A late callback must not admit someone after a timeout rejected them. Capture the player identity before awaiting work; do not assume a later global source still describes this connection.
Do not create two independent queues that both try to complete the same connection. Put checks behind one admission owner. Never log complete identifier sets or return database errors to the client.
Sources and next steps
The official playerConnecting reference defines the temporary source and deferral timing. Continue with server permissions, resource storage, or secure network events for checks after admission.