Skip to main content
The sh-licenses resource gives your server a structured, database-backed licensing system for professional credentials. You define every license type — business, medical, pharmaceutical, or anything your server requires — complete with price, expiry period, and per-license fields such as business name or registration number. Clerk NPCs at configured locations issue licenses through a NUI storefront, and players carry the resulting inventory items to prove their credentials. The resource integrates with sh-identity to pre-fill character details on the issuance form and uses Discord webhooks to log every transaction.

Framework Support

sh-licenses supports both VORP and RSG frameworks. Set the framework in your server-side config.lua:
config.lua
Config.Framework = 'auto'  -- 'auto', 'vorp', or 'rsg'
Setting 'auto' (the default) lets the resource detect your running framework automatically.

How the License System Works

Each license type is an entry in Config.Licenses with its own inventory item name, price, expiry period, allowed-jobs gate, and a set of custom fields the purchaser fills in. When a player visits a clerk NPC and purchases a license, the resource:
  1. Validates the purchaser’s identity (optionally via sh-identity) and checks job access.
  2. Charges the configured fee breakdown (base price + optional service, processing, and tax).
  3. Creates a database record and gives the player the corresponding inventory item.
  4. Posts a Discord webhook log entry if logging is enabled.
Players carry the item in their inventory and can use it to display the license document. Other resources can check license status by querying the database table (Config.Database.table, default: sh_licenses).

Defining License Types

Every license is an entry in Config.Licenses. Each entry requires a unique key, an inventory item name, a price, an expiry period, and any custom fields to collect at issuance. The allowedJobs list restricts which jobs can purchase the license — set to nil to allow any player:
config.lua
Config.Licenses = {
    business = {
        label        = 'Business License',
        item         = 'license_business',
        price        = 500,
        currencyType = 0,       -- 0 = cash, 1 = gold/bloodmoney
        expiresDays  = 60,
        allowMultiple = false,
        allowedJobs  = { 'realtor' },   -- nil = open to all
        fields = {
            { key = 'business_name',     label = 'Business Name',    type = 'text',   required = true,  max = 60 },
            { key = 'business_type',     label = 'Business Type',    type = 'select', required = true,  optionsFrom = 'BusinessTypes' },
            { key = 'business_location', label = 'Business Location', type = 'text',  required = true,  max = 60 },
        },
    },

    doctor = {
        label        = 'Doctor License',
        item         = 'license_doctor',
        price        = 550,
        currencyType = 0,
        expiresDays  = 30,
        allowMultiple = false,
        allowedJobs  = { 'doctor', 'medic' },
        fields = {
            { key = 'hospital_name',      label = 'Hospital / Clinic', type = 'text', required = true, max = 60 },
            { key = 'specialty',          label = 'Specialty',         type = 'text', required = true, max = 40 },
            { key = 'medical_license_no', label = 'Medical License #', type = 'text', required = true, max = 30 },
        },
    },
}
Add as many license types as your server needs. Remove any defaults that do not apply to your gameplay loop.

Default License Types

The following licenses are included in the default configuration as a starting point:
Issued to players operating a business. Fields include business name, type (chosen from a configurable list), and location. Priced at $500 by default with a 60-day expiry. Restricted to the realtor job by default — edit allowedJobs to match your server’s issuing role.
Required to legally operate a grow operation. Collects farm name and location. Priced at $2,000 with a 30-day expiry. Restricted to cannabis, cannabisfarmer, and farmer jobs by default.
Issued to licensed pharmacists. Collects pharmacy name, location, and registration number. Priced at $600 with a 60-day expiry.
Issued to practicing physicians. Collects clinic name, specialty, and medical license number. Priced at $550 with a 30-day expiry.
Issued to nursing staff. Collects hospital name, department, and nurse license number. Priced at $125 with a 30-day expiry.

Clerk NPC Configuration

Clerk NPCs are placed at configured world locations and handle license issuance. You can restrict which jobs are permitted to operate the clerk desk using Config.ClerkAccess:
config.lua
Config.Clerk = {
    enabled  = true,
    model    = `MP_U_M_M_BANKPRISONER_01`,
    scenario = 'WORLD_HUMAN_SMOKE_NERVOUS_STRESSED',
    interactDistance = 2.0,
    drawTextDistance = 6.0,
    interactControl  = 0xE30CD707,  -- R
    text3d   = '[R] Registration Clerk',
    blip = {
        enabled    = true,
        sprite     = 587827268,
        scale      = 0.2,
        color      = 2,
        shortRange = false,
        name       = 'Registration Clerk',
    },
    locations = {
        { coords = vector4(-795.90, -1212.44, 43.48, 218.27) },
        -- add more locations as needed
    },
}

-- Jobs allowed to operate the issuance desk
Config.ClerkAccess = {
    enabled     = true,
    allowedJobs = { 'registrar', 'clerk', 'mayor', 'marshal', 'realtor', 'doctor' },
}

Fee Breakdown

Each license has its own base price. On top of that, you can configure optional service charges, processing fees, and a tax percentage that apply globally across all license purchases:
config.lua
Config.Fees = {
    enabled         = true,
    serviceFlat     = 0,      -- flat bureau service fee added to every purchase
    servicePercent  = 0,      -- percentage of base price added as service rate
    processingFlat  = 0,      -- additional flat processing fee
    taxPercent      = 0,      -- tax applied after all other fees
    rounding        = 'nearest', -- 'none', 'nearest', 'up', or 'down'
}

Issuance Modes

The clerk desk supports two issuance modes selectable from the NUI:
  • Self — the license is issued directly to the clerk’s own character. Useful when a player is both the clerk and the recipient.
  • Nearest — the license is issued to the nearest player within Config.Issuance.targetRadius (default: 4.0 m). The target must remain in range during the transaction.
config.lua
Config.Issuance = {
    defaultMode          = 'self',   -- 'self' or 'nearest'
    allowModeSelection   = true,
    targetRadius         = 4.0,
    chargeTo             = 'self',   -- 'self', 'issuer', or 'target'
}

sh-identity Integration

When sh-identity is running alongside sh-licenses, identity fields on the license form are automatically pre-filled from the character’s registered real ID record. This prevents players from entering mismatched details:
config.lua
Config.IdentityLink = {
    enabled             = true,
    provider            = 'sh-identity',
    requireRealId       = false,  -- block purchase if no real ID exists
    lockPrefilledFields = true,   -- prevent editing pre-filled fields
    fallbackToCharacter = true,   -- fall back to framework data if no real ID
}

Discord Logging

Every license purchase is logged to a configurable Discord webhook:
config.lua
Config.Webhook = {
    enabled      = true,
    url          = '',  -- your Discord webhook URL
    username     = 'License Clerk',
    logPurchases = true,
    -- Privacy toggles for sensitive fields
    includeAddress = false,
    includeDob     = false,
}

License Persistence

Licenses are stored per-character in the database table specified by Config.Database.table (default: sh_licenses). They persist across server restarts and character sessions. Each character maintains an independent license set. The Config.Database.preferDb option (default: true) ensures the display always reads from the database record rather than item metadata.

Installation

1

Add the resource

Copy the sh-licenses folder into your server’s resources directory.
2

Add to server.cfg

Ensure sh-licenses after your framework and before any resource that checks license status:
server.cfg
ensure vorp_core     # or rsg-core
ensure sh-identity   # optional, for identity pre-fill
ensure sh-licenses
3

Import the SQL file

Run the SQL file from the sql/ folder of the resource against your database to create the character license records table.
4

Register inventory items

Register each license item name (e.g. license_business, license_doctor) as a usable item in your framework’s item registry. Item definition templates are included in the install/ folder.
5

Configure licenses and clerk

Edit Config.Licenses to define your license types, prices, and fields. Edit Config.Clerk.locations to position your clerk NPC, and set Config.ClerkAccess.allowedJobs to match the jobs that can operate the desk on your server.
6

Restart and verify

Restart the resource, log in with an authorized character, approach the clerk NPC, and purchase a test license. Confirm the license item is added to inventory and the record appears in the database.
Changing a license’s item value after it has been issued to characters will orphan existing inventory items. Always use consistent item names from initial setup. If a rename is unavoidable, perform a manual inventory migration for affected characters.