esx_license
esx_license stores player licenses such as driving, weapon and boat licenses in ESX Legacy. Server events, client callbacks, permissions and SQL explained.
esx_license keeps track of which licenses each player owns, for example a driving license or a weapon permit. Other resources such as esx_dmvschool, esx_boat, esx_vehicleshop and esx_weaponshop grant and check licenses through it.
Overview
| Fact | Value |
|---|---|
| Resource | esx_license |
| Side | Server only |
| Dependencies | esx_lib, es_extended, oxmysql |
| SQL | esx_license.sql (licenses, user_licenses) |
Installation
Import the SQL
CREATE TABLE `licenses` (
`type` varchar(60) NOT NULL,
`label` varchar(60) NOT NULL,
PRIMARY KEY (`type`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE `user_licenses` (
`id` int NOT NULL AUTO_INCREMENT,
`type` varchar(60) NOT NULL,
`owner` VARCHAR(60) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uq_user_licenses_owner_type` (`owner`, `type`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;licenses is the catalog of license types. The addons that use a license insert their own rows, for example:
INSERT INTO `licenses` (`type`, `label`) VALUES ('weapon', 'Weapon License');Start it
ensure esx_licenseOn start the resource checks user_licenses for duplicate owner + type rows. If it finds any it stops granting licenses and prints Schema migration failed; grants disabled. Remove the duplicates and restart. If the unique key is missing it adds it. The same migration is shipped as sql/security_hardening_2026_10_02.sql in the addons repo; see the addons overview.
Configuration
| Key | Default | Description |
|---|---|---|
Config.allowedJobs | { ['police'] = true } | Jobs that may read and remove other players' licenses. |
Config.LicenseCheckDistance | 5.0 (not in config.lua) | Max distance in meters between an allowed-job player and the target. Add the key to config.lua to change it. |
Who may read another player's licenses
A client request for another player passes when one of these is true:
- The caller asks about themselves.
- The caller's group is
adminorsuperadmin. - The caller's job is in
Config.allowedJobs, the job'sonDutyis notfalse, and the target is withinConfig.LicenseCheckDistance.
Server events
Local events for other server scripts. Clients cannot trigger them, except removeLicense.
esx_license:addLicense
TriggerEvent('esx_license:addLicense', playerId, licenseType, cb)| Name | Type | Description |
|---|---|---|
playerId | number | Server ID of an online player. |
licenseType | string | A type from the licenses table. |
cb | function or nil | Called with false if the player already owns it, otherwise with the insert result. |
TriggerEvent('esx_license:addLicense', source, 'weapon', function(result)
if result then
TriggerClientEvent('esx:showNotification', source, 'Weapon license issued')
end
end)If licenseType is not in the licenses table, the license is not added, a warning is printed and cb is never called.
esx_license:checkLicense
TriggerEvent('esx_license:checkLicense', playerId, licenseType, cb)cb(hasLicense) receives a boolean. Nothing happens if the player is offline.
TriggerEvent('esx_license:checkLicense', source, 'drive', function(hasLicense)
if not hasLicense then
return TriggerClientEvent('esx:showNotification', source, 'You need a driving license')
end
-- continue
end)esx_license:getLicenses
TriggerEvent('esx_license:getLicenses', playerId, cb)cb(licenses) receives an array of { type, label } for an online player.
esx_license:getLicense
TriggerEvent('esx_license:getLicense', licenseType, cb)cb({ type = licenseType, label = label }). label is nil when the type does not exist. This reads the catalog; it does not check a player.
esx_license:getLicensesList
TriggerEvent('esx_license:getLicensesList', function(list)
for _, license in ipairs(list) do
print(license.type, license.label)
end
end)Returns the cached catalog, loaded on start and refreshed on esx:sqlCatalogReady.
esx_license:removeLicense (net event)
TriggerServerEvent('esx_license:removeLicense', targetId, licenseType)| Name | Type | Description |
|---|---|---|
targetId | number | Server ID of the player who loses the license. |
licenseType | string | License type. |
Security
This is the only event clients can send. The server checks that the caller's job is in Config.allowedJobs, the license type exists, and the caller may read the target (see the rules above). Otherwise the caller gets "Your job is not allowed to remove the license".
To remove a license from server code, delete the row yourself:
local xTarget = ESX.Player(targetId)
MySQL.update('DELETE FROM user_licenses WHERE type = ? AND owner = ?', {
'weapon', xTarget.getIdentifier(),
})Client callbacks
Call with ESX.TriggerServerCallback, ESX.AwaitServerCallback or xLib.callback.
| Callback | Arguments | Returns |
|---|---|---|
esx_license:checkLicense | targetId, licenseType | boolean |
esx_license:getLicenses | targetId? | array of { type, label } (empty when denied) |
esx_license:getLicense | licenseType | { type, label } |
esx_license:getLicensesList | none | array of { type, label } |
targetId defaults to the caller when omitted. Reading someone else uses the permission rules above.
ESX.TriggerServerCallback('esx_license:checkLicense', function(hasLicense)
if hasLicense then
OpenGunShop()
else
ESX.ShowNotification('You need a weapon license')
end
end, GetPlayerServerId(PlayerId()), 'weapon')Common issues
- addLicense does nothing: the type is missing in
licenses, or the player is offline. Check the server console forMissing license type in db. - Grants disabled after update: duplicate rows in
user_licenses. Remove them and restart. - Police cannot see licenses: the officer is too far away (5 m), off duty, or the job is not in
Config.allowedJobs.
Related
Frequently asked questions
Independent documentation
Last updated on
esx_jobs
Configure esx_jobs for ESX Legacy, the miner, lumberjack, fisherman, fueler, slaughterer and tailor jobs. Zone format, vehicle deposits, events and fixes.
esx_lscustom
esx_lscustom adds Los Santos Customs tuning to ESX Legacy with a cart-based NUI, live stats and server-validated prices. Setup, config, pricing and events.