> ## Documentation Index
> Fetch the complete documentation index at: https://docs.aps-store.com/llms.txt
> Use this file to discover all available pages before exploring further.

# documents.lua

Orderable document types and the built-in document system. More in the [Document Office](https://docs.aps-store.com/assets/aps_cityhall/guides/document-office.md) guide.

> [!NOTE]
>
> Runs on: **shared**. This file stays editable when the resource is escrow-protected. Restart the resource after editing.

Players order documents with a validity period. Expired documents are shown as such.

```lua theme={null}
{
    id          = 'badge',
    name        = 'Badge',
    description = 'Official law enforcement badge.',
    icon        = 'BadgeCheck',   -- Car, Shield, CreditCard, BadgeCheck, Scale, HeartPulse, FileText
    allowedJobs = { 'police', 'sheriff' },   -- optional, nil = everyone
    minGrade    = 2,                         -- optional
    options     = {
        { label = '30 Days', days = 30,  price = 0 },
        { label = '1 Year',  days = 365, price = 0 },
    },
},
```

Restricted documents are shown greyed out with the required job underneath. Add `item = 'id_card'` to give an inventory item on order (see [Compatibility](https://docs.aps-store.com/assets/aps_cityhall/compability.md)). After ordering, `Config.OnDocumentOrdered` runs.

## Document items

Every document type with an `item` becomes a real document: a serial number, the holder's webcam photo, a signature and the fields you list. No extra script is needed.

```lua theme={null}
-- config/documents.lua
Config.DocumentSystem = {
    system       = 'auto',   -- 'auto' | 'builtin' | 'vms_documentsv2'
    showDistance = 3.0,
    showSeconds  = 10,
    reprintPrice = 50,       -- My documents → Print copy
    checkCommand = { enabled = true, name = 'checkdocument', jobs = { police = 0, sheriff = 0, government = 0 } },
}

{
    id       = 'id_card',
    name     = 'ID Card',
    item     = 'id_card',     -- given on order
    prefix   = 'ID-',         -- serial → ID-482913
    template = 'card',        -- 'card' | 'badge'
    fields   = { 'firstName', 'lastName', 'dateOfBirth', 'sex', 'height', 'nationality', 'ssn' },
    vms      = 'id_card',     -- name in vms_documentsv2
    options  = { … },
},
```

* **Fields:** `firstName`, `lastName`, `dateOfBirth`, `sex`, `height`, `nationality`, `ssn`, `job`, `jobGrade`. The data is saved when the document is issued (like a printed card). Fields the framework doesn't have are left out.
* **SSN:** ESX's own `users.ssn` is used; on other frameworks City Hall gives every character a unique SSN (`aps_citizens`).
* **Using the item** shows the document to the holder and everyone within `showDistance` as an ID-1 card in US driver-license layout (field codes, ghost photo, printed skyline), with a live *VALID* hologram or *EXPIRED / REVOKED* stamp. H (`flipKey`) turns it over to the back side (barcodes, restrictions, issuer) and back; Backspace (`hideKey`) puts it away early. `template = 'badge'` uses a dark badge-case layout.
* **/checkdocument** lets the listed jobs (job = minimum grade) look up any document.
* **Lost items:** *My documents → Print copy* prints the same document again for `reprintPrice`. Revoking a document in the Government Portal invalidates every copy.
* **Items:** ESX inventory — `sql/items_esx.sql`. ox\_inventory — add `server = { export = 'aps_cityhall.useDocument' }` and `consume = 0` to each document item (see [License cards → Items](https://docs.aps-store.com/assets/aps_cityhall/guides/document-cards-and-webcam.md) for the format).

***

## Full View

Preview File Updated: 3.7.0 - 02.11.2026

```lua config/documents.lua lines expandable theme={null}
Config = Config or {}

-- ─────────────────────────────────────────────
--  Document system
--    system   'auto'            vms_documentsv2 when it is running, else built-in
--             'builtin'         City Hall's own documents (no extra script needed)
--             'vms_documentsv2' documents are issued and invalidated through
--                               vms_documentsv2 (uses each type's `vms` name)
--
--  Built-in documents are inventory items with a serial number, the holder's
--  photo (webcam, see License Center → My Licenses), a signature and the
--  fields below. Using the item shows it to the holder and nearby players.
--    showDistance / showSeconds   who sees a shown document, and how long
--    hideKey / flipKey            keys to put it away early / turn it over (FiveM control ids)
--    reprintPrice                 price of a copy of a valid document (lost item)
--    checkCommand                 /checkdocument <serial> for these jobs
--                                 (job = minimum grade) — shows the document
--                                 and whether it is valid, revoked or expired
-- ─────────────────────────────────────────────
Config.DocumentSystem = {
    system       = 'auto',
    showDistance = 3.0,
    showSeconds  = 10,
    hideKey      = 177,     -- control that puts a shown document away (177 = Backspace)
    flipKey      = 74,      -- control that turns it over to the back and back again (74 = H)
    reprintPrice = 50,      -- Document Office → My documents → Print copy
    -- Photo taken with the PC's webcam in the Document Office (MugShotBase64)
    --   required     a photo must exist before a document with a photo is ordered
    --   transparent  photo without background (slower, MugShotBase64's AI cut-out)
    photo = {
        required    = true,
        transparent = false,
    },
    checkCommand = {
        enabled = true,
        name    = 'checkdocument',
        jobs    = { police = 0, sheriff = 0, government = 0 },
    },
}

-- ─────────────────────────────────────────────
--  Document Types (City Hall — Document Office)
--
--  Each entry requires:
--    id          unique string stored in the DB
--    name        display name shown in the UI
--    description short description shown in the UI
--    icon        one of: Car, Bike, Truck, Anchor, Plane, Shield, CreditCard,
--                BadgeCheck, Scale, HeartPulse, FileText
--    options     list of validity options { label, days, price }
--
--  Optional restriction fields:
--    allowedJobs  { 'police', 'sheriff' }  — nil = any job may order
--    minGrade     2                         — nil / 0 = any grade
--
--  Optional document item (built-in system):
--    item      inventory item given on order (must exist in your inventory)
--    prefix    serial number prefix, e.g. 'ID-' → ID-482913
--    template  'card' (ID-card layout) | 'badge' (badge layout)
--    fields    printed on the document, in this order:
--              firstName, lastName, dateOfBirth, sex, height, nationality,
--              ssn, job, jobGrade, badgeNumber (from vms_bossmenu)
--              (fields that have no value are skipped)
--    class     license class printed on the card ('C', 'M' …), optional
--    vms       the matching vms_documentsv2 document (system 'vms_documentsv2')
--    photo     false = no photo on this document (default: photo)
--
--  License documents:
--    requiresLicense  'car'  — can only be ordered after passing that theory
--                     test in the License Center (ids from config/licenses.lua).
--                     If the license is revoked, every copy shows REVOKED.
--
--  All prices are deducted from Config.MoneyAccount.
-- ─────────────────────────────────────────────
Config.DocumentTypes = {
    {
        id          = 'driving_license',
        class       = 'C',
        item        = 'driving_license',
        prefix      = 'DL-',
        template    = 'card',
        fields      = { 'firstName', 'lastName', 'dateOfBirth', 'sex' },
        vms         = 'driving_license',
        requiresLicense = 'car',
        name        = 'Driving License',
        description = 'Official document certifying the right to operate a motor vehicle on public roads.',
        icon        = 'Car',
        options     = {
            { label = '30 Days', days = 30,  price = 250  },
            { label = '90 Days', days = 90,  price = 600  },
            { label = '1 Year',  days = 365, price = 1800 },
        },
    },
    {
        id          = 'motorcycle_license',
        class       = 'M',
        item        = 'license_motorcycle',
        prefix      = 'MC-',
        template    = 'card',
        fields      = { 'firstName', 'lastName', 'dateOfBirth', 'sex' },
        requiresLicense = 'motorcycle',
        name        = 'Motorcycle License',
        description = 'Licence card for motorcycles and mopeds.',
        icon        = 'Bike',
        options     = {
            { label = '90 Days', days = 90,  price = 500  },
            { label = '1 Year',  days = 365, price = 1500 },
        },
    },
    {
        id          = 'truck_license',
        class       = 'A',
        item        = 'license_truck',
        prefix      = 'TR-',
        template    = 'card',
        fields      = { 'firstName', 'lastName', 'dateOfBirth', 'sex' },
        requiresLicense = 'truck',
        name        = 'Truck License',
        description = 'Licence card for heavy goods vehicles and trailers.',
        icon        = 'Truck',
        options     = {
            { label = '90 Days', days = 90,  price = 800  },
            { label = '1 Year',  days = 365, price = 2400 },
        },
    },
    {
        id          = 'boat_license',
        class       = 'B',
        item        = 'license_boat',
        prefix      = 'BT-',
        template    = 'card',
        fields      = { 'firstName', 'lastName', 'dateOfBirth' },
        vms         = 'boat_license',
        requiresLicense = 'boat',
        name        = 'Boat License',
        description = 'Licence card for motorised watercraft.',
        icon        = 'Anchor',
        options     = {
            { label = '1 Year', days = 365, price = 1200 },
        },
    },
    {
        id          = 'pilot_license',
        class       = 'P',
        item        = 'license_pilot',
        prefix      = 'FL-',
        template    = 'card',
        fields      = { 'firstName', 'lastName', 'dateOfBirth', 'ssn' },
        vms         = 'flying_license',
        requiresLicense = 'pilot',
        name        = 'Pilot License',
        description = 'Licence card for aircraft and helicopters.',
        icon        = 'Plane',
        options     = {
            { label = '1 Year', days = 365, price = 3000 },
        },
    },
    {
        id          = 'ccw',
        item        = 'weapons_license',
        prefix      = 'WP-',
        template    = 'card',
        fields      = { 'firstName', 'lastName', 'dateOfBirth', 'ssn' },
        vms         = 'weapon_license',
        requiresLicense = 'firearms',
        name        = 'CCW Permit',
        description = 'Concealed Carry Weapon permit authorising the holder to carry a concealed firearm.',
        icon        = 'Shield',
        options     = {
            { label = '30 Days', days = 30,  price = 400  },
            { label = '90 Days', days = 90,  price = 1000 },
            { label = '1 Year',  days = 365, price = 3000 },
        },
    },
    {
        id          = 'id_card',
        item        = 'id_card',
        prefix      = 'ID-',
        template    = 'card',
        fields      = { 'firstName', 'lastName', 'dateOfBirth', 'sex', 'height', 'nationality', 'ssn' },
        vms         = 'id_card',
        name        = 'ID Card',
        description = 'Government-issued photo identification card for all residents.',
        icon        = 'CreditCard',
        options     = {
            { label = '30 Days', days = 30,   price = 100  },
            { label = '1 Year',  days = 365,  price = 800  },
            { label = '5 Years', days = 1825, price = 3000 },
        },
    },
    {
        id          = 'badge',
        item        = 'police_badge',
        prefix      = 'PD-',
        template    = 'badge',
        fields      = { 'firstName', 'lastName', 'job', 'jobGrade', 'badgeNumber' },
        vms         = 'police_badge',
        name        = 'Badge',
        description = 'Official Law Enforcement badge issued by the city to active officers.',
        icon        = 'BadgeCheck',
        allowedJobs = { 'police', 'sheriff', 'fbi' },
        minGrade    = 0,
        options     = {
            { label = '30 Days', days = 30,  price = 500  },
            { label = '90 Days', days = 90,  price = 1200 },
            { label = '1 Year',  days = 365, price = 4000 },
        },
    },
    {
        id          = 'law_license',
        item        = 'law_license',
        prefix      = 'LL-',
        template    = 'card',
        fields      = { 'firstName', 'lastName' },
        name        = 'Law License',
        description = 'Legal practitioner license required to represent clients in court proceedings.',
        icon        = 'Scale',
        allowedJobs = { 'lawyer', 'judge' },
        minGrade    = 0,
        options     = {
            { label = '30 Days', days = 30,  price = 600  },
            { label = '90 Days', days = 90,  price = 1500 },
            { label = '1 Year',  days = 365, price = 5000 },
        },
    },
    {
        id          = 'health_insurance',
        item        = 'health_card',
        prefix      = 'HI-',
        template    = 'card',
        fields      = { 'firstName', 'lastName', 'dateOfBirth', 'ssn' },
        name        = 'Health Insurance',
        description = 'Health insurance policy covering medical services at registered facilities.',
        icon        = 'HeartPulse',
        options     = {
            { label = '3 Days',  days = 3,  price = 150  },
            { label = '7 Days',  days = 7,  price = 300  },
            { label = '30 Days', days = 30, price = 900  },
            { label = '90 Days', days = 90, price = 2200 },
        },
    },
}
```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.