> For the complete documentation index, see [llms.txt](https://docs.lgmods.co.uk/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.lgmods.co.uk/scripts/lgmods-licensing.md).

# LGMods Licensing

## LGMods Licensing

UK-style driving licence system for FiveM. Players apply at a front desk, receive a physical inventory item, and police can manage applications, points, bans, and revocations.

{% hint style="warning" %}
**Resource folder name must be `LGMods_Licensing`**

Exports are called as `exports['LGMods_Licensing']:...`. If you rename the resource folder, those calls (and inventory item export strings) will break unless you update them everywhere.
{% endhint %}

***

### Features

* Front desk offices (blips, optional NPCs, interact key) to apply for licences
* Licence categories with auto-issue or police approval
* Physical inventory item with metadata (where the inventory supports it)
* Police management UI: applications, search, points, ban, revoke, add category
* Penalty points with history and auto-expiry
* DVLA-style licence number generation
* ESX / QBCore frameworks
* ox\_inventory / qs-inventory / qb-inventory / ESX inventory

***

### Requirements

| Dependency                                         | Required                                      |
| -------------------------------------------------- | --------------------------------------------- |
| [oxmysql](https://github.com/overextended/oxmysql) | Yes                                           |
| ESX (`es_extended`) **or** QBCore (`qb-core`)      | Yes (one of them)                             |
| Matching inventory for `Config.InventorySystem`    | Yes                                           |
| ox\_lib                                            | Optional (notifications / menus when enabled) |

***

### Installation

1. Place the resource in your server resources folder as **`LGMods_Licensing`**.
2. Ensure start order: framework → inventory → oxmysql → this resource.

```cfg
ensure oxmysql
ensure es_extended   # or qb-core
ensure ox_inventory  # or your inventory
ensure LGMods_Licensing
```

3. Open `config.lua` and set:
   * `Config.Framework` - `'auto'`, `'esx'`, or `'qbcore'`
   * `Config.InventorySystem` - must match the inventory you actually use
4. Import `sql/install.sql` into your database (or let the script auto-create tables on start).
5. **Register the item `drivers_license` in your inventory** - see Inventory item setup. This is the most common cause of “exports / item not working”.
6. Set front desk coords, police jobs, costs, and categories as needed.
7. Restart the resource and test with `/mylicense` and the front desk.

{% hint style="info" %}
Tables are created automatically on start if missing. Running `sql/install.sql` manually is still recommended for clean installs.
{% endhint %}

***

### Configuration overview

All settings live in `config.lua`.

#### Framework & inventory

```lua
Config.Framework = 'auto' -- 'auto' | 'esx' | 'qbcore'
Config.InventorySystem = 'ox_inventory' -- 'ox_inventory' | 'qs-inventory' | 'qb-inventory' | 'esx'
```

#### Licence rules

* `Config.License.DefaultType` - `'full'` or `'provisional'`
* `Config.License.MaxPoints` - default `12` (licence treated as suspended at this total)
* `Config.License.PointExpiryDays` - default `84` (12 weeks)
* `Config.License.Categories` - each category has a `name` and `requiresApproval`

If `requiresApproval = false`, the licence / category is issued immediately at the front desk. If `true`, police must approve the application.

#### Front desk

* `MinAge` - default `17`
* `Cost` - cash cost (default `45`)
* `Locations` - coords, heading, blip
* `InteractionKey` - default `38` (E)
* `ShowBlips` / `SpawnNPCs`

#### Police

```lua
Config.Police = {
    RequiredJobs = {'police'},
    ShowLicenseDistance = 5.0
}
```

Add every job name that should access `/licenses` and police commands.

#### UI & exports

```lua
Config.UI.UseOxLib = true
Config.Exports.EnableAll = true -- keep this true
```

***

### Inventory item setup

{% hint style="danger" %}
**The item spawn name is hardcoded as `drivers_license`**

There is no config option to rename it. Your inventory item **must** be named exactly `drivers_license` (underscore, no spaces, no capital letters).
{% endhint %}

The script does **not** ship item definitions. You must add them to your inventory / framework yourself. If the item is missing, players can still get a DB licence but will not receive (or cannot use) the physical card.

#### Metadata the script writes

When a licence item is given, the script attaches:

| Field              | Description                     |
| ------------------ | ------------------------------- |
| `license_number`   | Raw licence number              |
| `formatted_number` | Spaced display form             |
| `name`             | Holder name                     |
| `dob`              | Date of birth                   |
| `type`             | `full` or `provisional`         |
| `categories`       | e.g. `A,B`                      |
| `points`           | Current penalty points          |
| `is_valid`         | Validity flag                   |
| `description`      | Short summary string            |
| `age`              | Calculated age (when available) |

* **ox\_inventory** stores this on `item.metadata`
* **qs-inventory / qb-inventory** store this on `item.info`
* **ESX** has no rich metadata - viewing pulls live data from the database

***

#### ox\_inventory

Set in config:

```lua
Config.InventorySystem = 'ox_inventory'
```

Add to `ox_inventory/data/items.lua` (or your items file):

```lua
['drivers_license'] = {
    label = 'Driving Licence',
    weight = 10,
    stack = false,
    close = true,
    consume = 0,
    client = {
        export = 'LGMods_Licensing.viewLicense'
    }
},
```

{% hint style="success" %}
**Recommended:** keep `stack = false` so each licence is unique and metadata does not merge incorrectly.
{% endhint %}

**Optional: buttons (view / show)**

If your ox\_inventory version supports item buttons, you can add:

```lua
['drivers_license'] = {
    label = 'Driving Licence',
    weight = 10,
    stack = false,
    close = true,
    consume = 0,
    client = {
        export = 'LGMods_Licensing.viewLicense'
    },
    buttons = {
        {
            label = 'Show to nearby',
            action = function(slot)
                exports['LGMods_Licensing']:showLicenseToNearby(slot)
            end
        }
    }
},
```

The resource also listens for ox item-use / right-click events for `drivers_license`, so using the item should open the licence UI once the item exists.

**Server export alternative**

If you prefer a **server** use export on the item:

```lua
['drivers_license'] = {
    label = 'Driving Licence',
    weight = 10,
    stack = false,
    close = true,
    consume = 0,
    server = {
        export = 'LGMods_Licensing.useDriversLicense'
    }
},
```

Both `viewLicense` and `useDriversLicense` are provided for inventory wiring.

Add an image as `ox_inventory/web/images/drivers_license.png` (optional but recommended).

***

#### qb-inventory (QBCore)

Set in config:

```lua
Config.InventorySystem = 'qb-inventory'
```

Add to `qb-core/shared/items.lua` (or your items shared file):

```lua
drivers_license = {
    name = 'drivers_license',
    label = 'Driving Licence',
    weight = 10,
    type = 'item',
    image = 'drivers_license.png',
    unique = true,
    useable = true,
    shouldClose = true,
    combinable = nil,
    description = 'A UK driving licence'
},
```

{% hint style="info" %}
`unique = true` and `useable = true` are important. Metadata is stored in `info`.
{% endhint %}

Place `drivers_license.png` in your inventory images folder (commonly `qb-inventory/html/images/`).

The script listens for `qb-inventory:client:UseItem` when the item name is `drivers_license`.

***

#### qs-inventory

Set in config:

```lua
Config.InventorySystem = 'qs-inventory'
```

Register the item in your qs-inventory items list / database with spawn name:

```
drivers_license
```

Example shared-style definition (adjust to your qs-inventory format):

```lua
['drivers_license'] = {
    name = 'drivers_license',
    label = 'Driving Licence',
    weight = 10,
    type = 'item',
    image = 'drivers_license.png',
    unique = true,
    useable = true,
    shouldClose = true,
    description = 'A UK driving licence'
},
```

The script listens for `qs-inventory:client:UseItem`. Metadata is stored in `info`.

If qs-inventory fails to add the item, the script may fall back to ESX `addInventoryItem` - the item must still exist in both places if you rely on that fallback.

***

#### ESX default inventory

Set in config:

```lua
Config.InventorySystem = 'esx'
```

Add the item to your `items` table (SQL example):

```sql
INSERT INTO `items` (`name`, `label`, `weight`, `rare`, `can_remove`)
VALUES ('drivers_license', 'Driving Licence', 1, 0, 1);
```

Or via your items management resource - spawn name must still be `drivers_license`.

{% hint style="warning" %}
ESX inventory does **not** store rich metadata on the item. Viewing the licence loads data from the LGMods database instead. Players can also use `/mylicense`.
{% endhint %}

If you use a usable-item system that fires `esx:useItem`, the script will open the licence UI for `drivers_license`.

***

### Quick checklist (item not working)

1. Config `InventorySystem` matches the inventory that is actually running.
2. Item name is exactly `drivers_license`.
3. Resource folder is exactly `LGMods_Licensing`.
4. Item is registered **and** the inventory was restarted after adding it.
5. Player has space / can carry the item (especially ox\_inventory).
6. For ox: `client.export` / `server.export` points at `LGMods_Licensing.viewLicense` or `LGMods_Licensing.useDriversLicense`.
7. For QB/QS: item is `useable = true` / `unique = true`.

***

### How the system works

#### Getting a licence

1. Go to a configured front desk location and press **E** (or your interaction key).
2. Choose licence type and category.
3. Age and cash are checked (`MinAge`, `Cost`).
4. If the category does **not** require approval → licence is created and the `drivers_license` item is given.
5. If it **does** require approval → an application is created and police are notified. On approval, the licence/category is granted and the item is given/updated.

#### Using the item

* Use the item in inventory to open the licence UI.
* Show to nearby players (within \~5m where supported).
* Police can verify against the database.

#### Validity

A licence is treated as **invalid** if:

* It is revoked
* It is banned (`banned_until` still in the future)
* Points are at or above `Config.License.MaxPoints` (default 12)

Points expire after `PointExpiryDays` and are cleaned up automatically.

***

### Commands

#### Everyone

| Command        | Description                  |
| -------------- | ---------------------------- |
| `/mylicense`   | View your own licence status |
| `/licensehelp` | Show available commands      |

#### Police (jobs in `Config.Police.RequiredJobs`)

| Command             | Usage                                               |
| ------------------- | --------------------------------------------------- |
| `/licenses`         | Open police licence management UI                   |
| `/addpoints`        | `/addpoints [id] [1-12] [reason]`                   |
| `/removepoints`     | `/removepoints [id] [points]`                       |
| `/banlicense`       | `/banlicense [id] [days] [reason]`                  |
| `/unbanlicense`     | `/unbanlicense [id]`                                |
| `/revokelicense`    | `/revokelicense [id] [reason]`                      |
| `/reinstatelicense` | `/reinstatelicense [id]`                            |
| `/grantlicense`     | `/grantlicense [id] [full\|provisional] [category]` |

`[id]` is the player’s **server ID**.

***

### Exports (for other scripts)

{% hint style="info" %}
For other resources, use the **player ID (server source)** exports below. Always call them with the resource name:

`exports['LGMods_Licensing']:ExportName(...)`
{% endhint %}

#### Recommended public API (server)

These are the exports you should use from other scripts.

**GrantLicense**

Creates a licence in the database and gives the inventory item.

```lua
local success, result = exports['LGMods_Licensing']:GrantLicense(playerId, 'full', 'B')
-- success = true/false
-- result = license number on success, or error message on failure
```

* `playerId` (number) - required
* `licenseType` (string) - `'full'` or `'provisional'` (default `'full'`)
* `category` (string) - e.g. `'B'` (default `'B'`)

Fails if the player already has a licence.

**RevokePlayerLicense**

```lua
local success, message = exports['LGMods_Licensing']:RevokePlayerLicense(playerId, 'Fraud')
```

**BanPlayerLicense**

```lua
local success, message = exports['LGMods_Licensing']:BanPlayerLicense(playerId, 7, 'Dangerous driving')
-- banDuration is in days
```

**UnbanPlayerLicense**

```lua
local success, message = exports['LGMods_Licensing']:UnbanPlayerLicense(playerId)
```

**ReinstatePlayerLicense**

```lua
local success, message = exports['LGMods_Licensing']:ReinstatePlayerLicense(playerId)
```

**AddPoints / RemovePoints**

```lua
local success, message = exports['LGMods_Licensing']:AddPoints(playerId, 3, 'Speeding')
local success, message = exports['LGMods_Licensing']:RemovePoints(playerId, 2)
```

**HasValidLicense**

```lua
local isValid, reason = exports['LGMods_Licensing']:HasValidLicense(playerId)
if not isValid then
    print(reason) -- e.g. "No license", "License revoked", "License banned", "License suspended (12+ points)"
end
```

**HasLicense**

```lua
local hasLicense = exports['LGMods_Licensing']:HasLicense(playerId) -- true/false
```

**GetPlayerLicenseStatus**

```lua
local license = exports['LGMods_Licensing']:GetPlayerLicenseStatus(playerId)
if license then
    print(license.license_number, license.points, license.type)
end
```

**GetLicenseByNumber**

```lua
local license = exports['LGMods_Licensing']:GetLicenseByNumber(licenseNumber)
```

**GetPlayerPointsHistory**

```lua
local history = exports['LGMods_Licensing']:GetPlayerPointsHistory(playerId, 10)
-- second arg is optional limit (default 10)
```

#### Inventory / item-use exports (server)

Used when wiring inventory items - not usually needed for gameplay scripts.

```lua
exports['LGMods_Licensing']:viewLicense(playerId, item)
exports['LGMods_Licensing']:showLicenseToPlayer(playerId, targetId, item)
exports['LGMods_Licensing']:useDriversLicense(playerId, item)
```

#### Inventory helpers (server)

Useful if you need to give/remove/update the physical item yourself:

| Export                                             | Purpose                      |
| -------------------------------------------------- | ---------------------------- |
| `GiveLicenseItem(playerId, licenseData)`           | Give the item with metadata  |
| `RemoveLicenseItem(playerId, licenseNumber)`       | Remove matching item         |
| `UpdateLicenseItemMetadata(playerId, licenseData)` | Refresh item metadata        |
| `HasLicenseItem(playerId, licenseNumber)`          | Check if player has the card |
| `RemoveAllLicenseItems(playerId)`                  | Remove all licence items     |
| `GetLicenseMetadata(licenseData)`                  | Build metadata table         |

Example:

```lua
local license = exports['LGMods_Licensing']:GetPlayerLicenseStatus(playerId)
if license then
    exports['LGMods_Licensing']:GiveLicenseItem(playerId, license)
end
```

#### Client exports (UI / item)

```lua
-- Open / close licence UI
exports['LGMods_Licensing']:ShowLicenseUI(licenseData)
exports['LGMods_Licensing']:HideLicenseUI()

-- Item helpers
exports['LGMods_Licensing']:viewLicense(item)
exports['LGMods_Licensing']:showLicenseToNearby(item)
```

For ox\_inventory item definitions, the usual string is:

```
LGMods_Licensing.viewLicense
```

***

### Example integrations

#### Block driving without a valid licence

```lua
-- server-side example
RegisterNetEvent('your_resource:checkCanDrive', function()
    local src = source
    local ok, reason = exports['LGMods_Licensing']:HasValidLicense(src)
    if not ok then
        TriggerClientEvent('ox_lib:notify', src, {
            title = 'Licence',
            description = reason or 'No valid driving licence',
            type = 'error'
        })
        return
    end
    -- allow whatever you need
end)
```

#### Add points from another script

```lua
local success, message = exports['LGMods_Licensing']:AddPoints(targetId, 3, 'Ran red light')
```

#### Grant a licence from an admin menu

```lua
local success, licenseNumber = exports['LGMods_Licensing']:GrantLicense(targetId, 'full', 'B')
if success then
    print('Granted: ' .. licenseNumber)
else
    print('Failed: ' .. tostring(licenseNumber))
end
```

***

### Database

Default tables (names configurable in `Config.Database`):

* `lgmods_licenses` - main licence records (one per citizen)
* `lgmods_license_points` - points history
* `lgmods_license_applications` - pending / processed applications

SQL file: `sql/install.sql`.

***

### Troubleshooting

#### “Exports don’t work”

1. Resource name is `LGMods_Licensing` (exact).
2. Resource is started before the script that calls the export.
3. You are calling **server** exports from the **server**, and client exports from the **client**.
4. `Config.Exports.EnableAll` is `true`.
5. Use `exports['LGMods_Licensing']:HasValidLicense(source)` - not a made-up export name.

#### “Player got approved but no item”

1. Item `drivers_license` is not registered in the inventory.
2. Wrong `Config.InventorySystem`.
3. Inventory inventory is full / cannot carry.
4. Inventory resource was not restarted after adding the item.
5. On ESX, confirm the row exists in the `items` table.

#### “Using the item does nothing”

1. ox: missing `client.export` / `server.export`, or export string typo.
2. QB/QS: item not marked useable.
3. Item name is not exactly `drivers_license`.
4. Metadata missing (old/admin-given item without metadata) - try `/mylicense` or re-grant via `/grantlicense` / police tools.

#### “Police commands say no permission”

Add the player’s **exact** job name to `Config.Police.RequiredJobs` (e.g. `'police'`, `'sheriff'`).

#### Categories missing in the menu

Any category you add under `Config.License.Categories` should appear after a resource restart. Make sure the key and `name` / `requiresApproval` fields are valid Lua.

***

### Support notes

* Keep `config.lua` edits only - that file is listed for escrow ignore.
* Do not rename the item away from `drivers_license` unless you are prepared to edit the script itself.
* Prefer the player-ID exports (`GrantLicense`, `BanPlayerLicense`, `AddPoints`, etc.) for third-party integrations.
