FiveM & QBCore · Practical guide

How to Set Up a Whitelist on Your QBCore FiveM Server

A whitelist keeps your FiveM server private and maintains quality roleplay. This guide covers setting up a whitelist system on QBCore with Discord integration.

Stellar AI · Updated 8 September 2026 · 6 min read

A practical guide to how to set up a whitelist on your qbcore fivem server, with implementation decisions, validation steps, and security considerations for a production-minded project.

Overview

This guide explains how to implement a robust whitelist for a QBCore FiveM server. It covers architecture, secure server-side validation, database choices, a practical server-side implementation example, admin tooling, testing, and deployment/maintenance best practices. The patterns shown apply to QBCore and are easy to adapt to ESX or to frameworks using ox_lib. For related management tooling and monitoring you can explore Stellar AI's tools at https://trystellarai.com/app.

Architecture and Data Model

Keep the whitelist logic entirely on the server. The client cannot be trusted for any access control decision: do all validation inside the player connection flow and any command handlers. Typical components:

  • Connection gate (playerConnecting deferrals) — checks identifiers before allowing join.
  • Persistent store (MySQL/oxmysql) — canonical whitelist entries, metadata (who added, reason, expiration).
  • Admin controls — commands and optional UI for adding/removing entries.
  • Audit and logging — record join attempts and admin actions to detect abuse.

Minimal data model (SQL):

CREATE TABLE whitelist (
  id INT AUTO_INCREMENT PRIMARY KEY,
  identifier VARCHAR(64) NOT NULL, -- e.g. steam:110000...
  added_by VARCHAR(64),
  added_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  expires_at TIMESTAMP NULL,
  reason TEXT NULL,
  INDEX (identifier)
);

Implementation Choices

Storage

Use a server-side database rather than a local file for production. MySQL-backed stores (mysql-async or oxmysql) scale and persist across restarts. JSON or flat-file storage can be useful for testing or small communities but is fragile for production.

Identifiers to use

Whitelist by stable server-side identifiers: Steam ID ("steam:..."), Rockstar License ("license:..."), xbl or discord (when available). Do not rely on player-supplied names or client-side variables. Always parse and canonicalize identifiers on the server.

Server-Side Validation and Security

Enforce these rules:

  • Never trust client input. Do not allow the client to assert which identifier it is — retrieve identifiers server-side via GetPlayerIdentifiers in the playerConnecting event or via QBCore hooks.
  • Use deferrals. Use FiveM's deferrals to pause connection, run checks, and return clear messages to the client.
  • Rate-limit and log. Track repeated failed connection attempts and log them; consider temporary bans for obvious abuse vectors.
  • Sanitize database inputs. Use parameterized queries (MySQL.Async or your driver) rather than string concatenation.
  • Principle of least privilege. Grant the minimum database permissions required for the whitelist table (SELECT/INSERT/UPDATE/DELETE).

Practical Server Script Example (QBCore)

The example below demonstrates a server-side whitelist that uses FiveM deferrals and MySQL.Async. Adapt the DB calls to oxmysql or your preferred driver. The logic: collect identifiers, check DB, allow or deny via deferrals.

-- server.lua (example)
local QBCore = exports['qb-core']:GetCoreObject()

AddEventHandler('playerConnecting', function(name, setKickReason, deferrals)
  local src = source
  deferrals.defer()
  deferrals.update("Checking whitelist...")

  -- Collect identifiers server-side
  local identifiers = {}
  for _, id in ipairs(GetPlayerIdentifiers(src)) do
    identifiers[id:match('^(%w+:%w+)')] = id
  end

  -- Prefer steam or license, fallback to first identifier
  local identifier
  if identifiers['steam'] then identifier = identifiers['steam']
  elseif identifiers['license'] then identifier = identifiers['license']
  else
    for _,v in pairs(identifiers) do identifier = v; break end
  end

  if not identifier then
    deferrals.done("Could not resolve your identifiers. Ensure your client is running Steam/launcher.")
    return
  end

  -- Secure DB lookup (MySQL.Async shown; replace with oxmysql if used)
  MySQL.Async.fetchAll('SELECT * FROM whitelist WHERE identifier = @id', {
    ['@id'] = identifier
  }, function(result)
    if result and #result > 0 then
      deferrals.done() -- allow
    else
      deferrals.done("You are not whitelisted on this server.")
    end
  end)
end)

-- Admin commands (server-side only)
RegisterCommand('wladd', function(source, args, raw)
  if source ~= 0 and not IsPlayerAceAllowed(source, 'admin') then
    TriggerClientEvent('chat:addMessage', source, { args = { '^1SYSTEM', 'No permission' } })
    return
  end

  local identifier = args[1]
  if not identifier then
    print('Usage: /wladd ')
    return
  end

  MySQL.Async.execute('INSERT INTO whitelist (identifier, added_by) VALUES (@id, @added)', {
    ['@id'] = identifier, ['@added'] = (source == 0 and 'console' or tostring(GetPlayerName(source)))
  }, function(rows)
    print('Whitelist entry added:', identifier)
  end)
end, true)

Notes:

  • Adapt the admin permission check to your server's ACLs. IsPlayerAceAllowed is a common FiveM ACE check.
  • For QBCore admin menus, expose server commands and keep UI interactions purely as clients sending admin requests to the server; validate the caller server-side before applying changes.

Admin Tools and UI

Consider two admin interfaces:

  1. Command-line commands for quick adds/removals (as shown above).
  2. Context menus or web panels that call server endpoints. If you use ox_lib, register client context menus but perform all whitelist changes through server events that verify the admin's identity and permissions.

If you build a web dashboard, expose a server-side API (not client) that only trusted backend components call. Protect that API with keys and IP restrictions.

Testing and QA

Before production rollout:

  • Test fresh connections with non-whitelisted identifiers and confirm deferral messages are clear.
  • Test admin flows: add, remove, expire entries and verify changes apply immediately.
  • Test edge cases: multiple identifiers, identifier collisions, partial information (no steam), and DB failures.
  • Simulate DB downtime and ensure your server responds with a clear message (prefer safe denial rather than allowing everyone when DB unavailable).

For logging and quick diagnosis, log join attempts with timestamp, identifier, result, and admin actions. Keep logs in a separate retention storage for auditing.

Deployment and Maintenance

Deployment checklist:

Task Action Why
Staging test Deploy to staging server and run integration test suite Catch regressions before production
DB Backup Schedule nightly backups of whitelist table Recover from accidental deletes or corruption
Monitoring Monitor DB connectivity, deferral failure rates, and repeated rejects Detect outages or attack patterns early
Access control Review ACEs and admin accounts monthly Limit privilege creep

For operational tooling and analytics integration, you can connect server logs to external systems; to inspect and manage lists interactively consider secure apps such as https://trystellarai.com/app.

Maintenance Best Practices

Maintain whitelist hygiene:

  • Expire entries for temporary whitelists and implement automatic cleanup jobs.
  • Keep an audit trail (who added/removed and when).
  • Rotate database credentials periodically and restrict DB user permissions.
  • Document admin procedures: who can add, remove, and how appeals are handled.

For deeper procedural guidance and updates, review your team’s operations notes or an operations blog post: https://trystellarai.com/blog.

Roblox Considerations (Server Authority and RemoteEvents)

If you also maintain Roblox servers or scripts, follow these parallels:

  • Keep decisions server-authoritative. Clients may request actions via RemoteEvents, but the server must validate every request before changing whitelist state.
  • Use DataStore for persistence and implement robust failure handling: DataStore calls may fail, so implement retries and degrade gracefully (e.g., deny joins if you cannot confirm whitelist status reliably).
  • Log and audit RemoteEvent calls and DataStore errors to diagnose consistency issues.

Practical Checklist

  1. Choose a storage backend (MySQL/oxmysql preferred for production).
  2. Implement playerConnecting deferrals that look up identifiers server-side.
  3. Do not use client-provided identifiers; read them with GetPlayerIdentifiers.
  4. Provide secure admin commands with ACE checks or QBCore authority checks.
  5. Log and monitor join attempts and admin changes.
  6. Back up the whitelist table and test restore procedures.
  7. Test DB failure scenarios; choose deny-by-default behavior if DB unavailable.

Build your next system with Stellar AI

Describe one feature, get organized project files, then bring back your errors to keep improving. Start free with no card required. Test generated code in a private development environment before release.

Create your first script free →