Skip to Content
ScriptingConnection Deferrals

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

TestExpected result
Correct identifier mapped to trueConnection continues
Identifier missing, or mapped to falseApproval message, no admission
Invalid JSON or missing fileGeneric temporary-unavailability message; server log explains configuration failure
Resource starts with an empty objectNobody admitted; not an accidental open server
Two clients connect togetherEach 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.