> For the complete documentation index, see [llms.txt](https://feron-scripts.gitbook.io/welcome/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://feron-scripts.gitbook.io/welcome/scripts/feron-multicharacter/config/framework-slots.md).

# Framework, Slots & Tebex

Framework adapter, MySQL driver, notification override, identifier preference, slot limits, /relog, starter items, and Tebex premium-slot integration.

{% tabs %}
{% tab title="QBCore" %}

```lua

-- ════════════════════════════════════════════════════════════════
--  FRAMEWORK
-- ════════════════════════════════════════════════════════════════
-- 'qb'    → modern QBCore (export based)
-- 'oldqb' → legacy QBCore (TriggerEvent based)
-- 'qbx'   → qbox / qbx_core
--
-- NOTE: ESX support ships as a separate package (see Tebex listing).
-- This main edition is QBCore / Qbox only — the framework.lua
-- adapter does not include ESX paths.
Config.Framework = 'qb'

-- 'oxmysql' | 'mysql-async' | 'ghmattimysql'
-- (also update fxmanifest.lua dependency when changing this)
Config.Mysql = 'oxmysql'

-- If your framework export name is non-standard (forks, edits)
Config.UseCustomFrameworkExport = false
Config.CustomFrameworkExport    = 'qb-core'


-- ════════════════════════════════════════════════════════════════
--  NOTIFICATION ADAPTER
--  Override this to plug into your custom notify resource
--  (okokNotify, mythic_notify, ox_lib, etc.)
-- ════════════════════════════════════════════════════════════════
Config.Notify = function(text, notifyType)
    TriggerEvent('QBCore:Notify', text, notifyType or 'primary')
end


-- ════════════════════════════════════════════════════════════════
--  IDENTIFIER
--
--  Selects which FiveM identifier is used to track PREMIUM SLOTS,
--  redeemed Tebex keys, audit log entries and per-player
--  Config.PlayerSlotOverrides matches.
--
--  Available types (only those exposed by GetPlayerIdentifiers):
--    'license'   → Rockstar Social Club license (QBCore default).
--    'license2'  → Newer Rockstar license format.
--    'discord'   → Discord user id (only if Discord rich presence is on).
--    'steam'     → Steam64 id (only if launched with Steam running).
--    'xbl'       → Xbox Live id.
--    'live'      → Microsoft Live id.
--    'fivem'     → FiveM forum / Cfx.re id.
--    'ip'        → IP address. NOT recommended (NAT, dynamic IPs).
--
--  IMPORTANT: This does NOT affect QBCore character ownership —
--  the `players` table is always keyed by `license`. This setting
--  ONLY changes which identifier is written to feron_multichar_*
--  tables. Switching it after launch orphans existing rows.
-- ════════════════════════════════════════════════════════════════
Config.Identifier = {
    -- Primary identifier prefix (no trailing colon).
    primary  = 'license',

    -- If the primary is missing for a player, try these in order.
    -- The first one found is used. If you leave this empty (or nil),
    -- 'license' is used as an automatic safety fallback so a player
    -- can never be locked out of premium just because their preferred
    -- id type wasn't available — license is guaranteed by FiveM.
    fallback = { 'license2' },

    -- If true, players with NONE of (primary + fallback) cannot
    -- redeem keys / accumulate premium slots. Note: with the
    -- automatic 'license' safety fallback above, this almost never
    -- triggers — set fallback explicitly without 'license' to
    -- actually enforce a strict primary-only policy.
    requireAny = false,
}


-- ════════════════════════════════════════════════════════════════
--  SLOT SYSTEM
-- ════════════════════════════════════════════════════════════════
-- The roster panel scrolls automatically — set MaxCharSlots to
-- whatever fits your server. 5 is a tasteful default; values up
-- to ~10 still look clean. Each slot animates in with a staggered
-- fade so the panel never feels static.
Config.MaxCharSlots     = 5
Config.DefaultOpenSlots = 3    -- Slots open by default; rest are locked behind premium

-- Per-license overrides: grant specific players more open slots without a key.
-- Identifier can be: rockstar license, discord:id, steam:hex, or fivem:id
Config.PlayerSlotOverrides = {
    -- { identifier = 'license:abc1234567...', slots = 5 },
    -- { identifier = 'discord:000000000000', slots = 4 },
}

-- Server-side gate for character deletion. Set to false to make
-- the delete callback refuse — even if the UI button is somehow
-- still clickable. To hide the button cosmetically, also set
-- Config.UI.show.deleteButton = false.
Config.AllowDeleteCharacter = true


-- ════════════════════════════════════════════════════════════════
--  RELOG  —  Switch character mid-session.
--
--  Players type the configured command to leave their current
--  character (saves position + logs out) and re-open the
--  multicharacter selector. They can pick a different character
--  or create a new one without disconnecting.
-- ════════════════════════════════════════════════════════════════
Config.Relog = {
    enabled    = true,
    command    = 'relog',     -- registered as /relog
    permission = 'all',       -- 'all' | 'admin' | 'group:vip' (ace permission)
    cooldown   = 5,           -- seconds between relogs (anti-spam)
}


-- ════════════════════════════════════════════════════════════════
--  STARTER ITEMS  —  Granted to a freshly created character.
--
--  Items are added on the player's FIRST character select after
--  creation (so the inventory is hydrated). `idCard = true`
--  routes the item through Config.IdCardSystem so framework
--  metadata (citizenid, name, dob, gender) is attached.
--
--  Item names must match your inventory's item list exactly.
--  Leave the table empty to grant nothing.
-- ════════════════════════════════════════════════════════════════
Config.StarterItems = {
    -- { name = 'phone',          amount = 1 },
    -- { name = 'id_card',        amount = 1, idCard = true },
    -- { name = 'driver_license', amount = 1, idCard = true },
}

-- Which ID-card system handles the `idCard = true` items.
--   'auto' → detects qbx_idcard / um-idcard / bl_idcard at runtime
--   '<name>' → forces a specific system
--   'none' → falls back to plain item add (no metadata)
Config.IdCardSystem = 'auto'

-- Optional override: take full control of how an `idCard = true`
-- item is granted. Receives (src, charinfo, itemName, amount).
-- Set to nil to use the built-in IdCardSystem dispatch.
Config.OnGiveIdCard = nil


-- ════════════════════════════════════════════════════════════════
--  PREMIUM / TEBEX
-- ════════════════════════════════════════════════════════════════
-- HOW THIS WORKS
-- ──────────────
--   1. Customer buys a "Premium Slot" package from your Tebex store.
--   2. Tebex runs a SERVER COMMAND on your fivem server (configured in
--      the package settings, see TEBEX_SETUP.md):
--          feron_mc_redeem_premium {"transid":"{transaction_id}","slots":1}
--   3. Our server-side handler catches that command and inserts the
--      transid into the `feron_multichar_keys` table (is_used = 0).
--   4. The customer enters the code in the multichar UI.
--   5. Server validates (exists + unused), increments their
--      `feron_multichar_slots.extra_slots` by the slot amount, and marks
--      the key consumed.
--
-- Same flow you used in dockerize_vip — just adapted for slot grants.
-- ──────────────────────────────────────────────────────────────────
Config.Tebex = {
    Link            = 'https://feron.tebex.io/',
    Enabled         = true,

    -- Name of the command Tebex will run on your server when a package is purchased.
    -- Must match what you configure in the Tebex package "Online Command" field.
    ServerCommand   = 'feron_mc_redeem_premium',

    -- Tebex transaction IDs follow a strict format:
    --   tbx-XXXXXXXXXXXXXX-XXXXXX
    --   (4-char prefix, 14 hex chars, dash, 6 hex chars — lowercase)
    -- We validate against this format BEFORE hitting the DB, so junk
    -- input is rejected instantly without burning a query.
    -- The DB lookup is still the source of truth.
    KeyPattern      = '^tbx%-[a-f0-9]+%-[a-f0-9]+$',
    KeyMinLen       = 20,   -- conservative lower bound (covers tbx-X+ -X+)
    KeyMaxLen       = 64,   -- conservative upper bound

    -- Mark redeemed keys as used (is_used=1) so they cannot be reused.
    -- Set false to delete the row instead.
    KeepUsedKeys    = true,
}
```

{% endtab %}

{% tab title="ESX" %}

```lua

-- ════════════════════════════════════════════════════════════════
--  FRAMEWORK
-- ════════════════════════════════════════════════════════════════
-- 'esx' → ESX Legacy 1.10+ (export based, getSharedObject)
--
-- NOTE: QBCore support ships as a separate package (see Tebex listing).
-- This ESX edition's framework.lua does not include QBCore paths.
Config.Framework = 'esx'

-- 'oxmysql' | 'mysql-async' | 'ghmattimysql'
-- (also update fxmanifest.lua dependency when changing this)
Config.Mysql = 'oxmysql'


-- ════════════════════════════════════════════════════════════════
--  NOTIFICATION ADAPTER
--  Override this to plug into your custom notify resource
--  (okokNotify, mythic_notify, ox_lib, etc.)
-- ════════════════════════════════════════════════════════════════
Config.Notify = function(text, notifyType)
    -- ESX Legacy default. notifyType maps roughly: 'primary' → 'info',
    -- 'success' → 'success', 'error' → 'error'. ESX's vanilla
    -- esx:showNotification only takes the text; modern forks accept
    -- (msg, type, length). We pass type defensively.
    TriggerEvent('esx:showNotification', text, notifyType or 'info')
end


-- ════════════════════════════════════════════════════════════════
--  IDENTIFIER
--
--  Selects which FiveM identifier is used to track PREMIUM SLOTS,
--  redeemed Tebex keys, audit log entries and per-player
--  Config.PlayerSlotOverrides matches.
--
--  Available types (only those exposed by GetPlayerIdentifiers):
--    'license'   → Rockstar Social Club license (QBCore default).
--    'license2'  → Newer Rockstar license format.
--    'discord'   → Discord user id (only if Discord rich presence is on).
--    'steam'     → Steam64 id (only if launched with Steam running).
--    'xbl'       → Xbox Live id.
--    'live'      → Microsoft Live id.
--    'fivem'     → FiveM forum / Cfx.re id.
--    'ip'        → IP address. NOT recommended (NAT, dynamic IPs).
--
--  IMPORTANT: This does NOT affect QBCore character ownership —
--  the `players` table is always keyed by `license`. This setting
--  ONLY changes which identifier is written to feron_multichar_*
--  tables. Switching it after launch orphans existing rows.
-- ════════════════════════════════════════════════════════════════
Config.Identifier = {
    -- Primary identifier prefix (no trailing colon).
    primary  = 'license',

    -- If the primary is missing for a player, try these in order.
    -- The first one found is used. If you leave this empty (or nil),
    -- 'license' is used as an automatic safety fallback so a player
    -- can never be locked out of premium just because their preferred
    -- id type wasn't available — license is guaranteed by FiveM.
    fallback = { 'license2' },

    -- If true, players with NONE of (primary + fallback) cannot
    -- redeem keys / accumulate premium slots. Note: with the
    -- automatic 'license' safety fallback above, this almost never
    -- triggers — set fallback explicitly without 'license' to
    -- actually enforce a strict primary-only policy.
    requireAny = false,
}


-- ════════════════════════════════════════════════════════════════
--  SLOT SYSTEM
-- ════════════════════════════════════════════════════════════════
-- The roster panel scrolls automatically — set MaxCharSlots to
-- whatever fits your server. 5 is a tasteful default; values up
-- to ~10 still look clean. Each slot animates in with a staggered
-- fade so the panel never feels static.
Config.MaxCharSlots     = 5
Config.DefaultOpenSlots = 3    -- Slots open by default; rest are locked behind premium

-- Per-license overrides: grant specific players more open slots without a key.
-- Identifier can be: rockstar license, discord:id, steam:hex, or fivem:id
Config.PlayerSlotOverrides = {
    -- { identifier = 'license:abc1234567...', slots = 5 },
    -- { identifier = 'discord:000000000000', slots = 4 },
}

-- Server-side gate for character deletion. Set to false to make
-- the delete callback refuse — even if the UI button is somehow
-- still clickable. To hide the button cosmetically, also set
-- Config.UI.show.deleteButton = false.
Config.AllowDeleteCharacter = true


-- ════════════════════════════════════════════════════════════════
--  RELOG  —  Switch character mid-session.
--
--  Players type the configured command to leave their current
--  character (saves position + logs out) and re-open the
--  multicharacter selector. They can pick a different character
--  or create a new one without disconnecting.
-- ════════════════════════════════════════════════════════════════
Config.Relog = {
    enabled    = true,
    command    = 'relog',     -- registered as /relog
    permission = 'all',       -- 'all' | 'admin' | 'group:vip' (ace permission)
    cooldown   = 5,           -- seconds between relogs (anti-spam)
}


-- ════════════════════════════════════════════════════════════════
--  STARTER ITEMS  —  Granted to a freshly created character.
--
--  Items are added on the player's FIRST character select after
--  creation (so the inventory is hydrated). `idCard = true`
--  routes the item through Config.IdCardSystem so framework
--  metadata (citizenid, name, dob, gender) is attached.
--
--  Item names must match your inventory's item list exactly.
--  Leave the table empty to grant nothing.
-- ════════════════════════════════════════════════════════════════
Config.StarterItems = {
    -- { name = 'phone',          amount = 1 },
    -- { name = 'id_card',        amount = 1, idCard = true },
    -- { name = 'driver_license', amount = 1, idCard = true },
}

-- Which ID-card system handles the `idCard = true` items.
--   'auto' → detects qbx_idcard / um-idcard / bl_idcard at runtime
--   '<name>' → forces a specific system
--   'none' → falls back to plain item add (no metadata)
Config.IdCardSystem = 'auto'

-- Optional override: take full control of how an `idCard = true`
-- item is granted. Receives (src, charinfo, itemName, amount).
-- Set to nil to use the built-in IdCardSystem dispatch.
Config.OnGiveIdCard = nil


-- ════════════════════════════════════════════════════════════════
--  PREMIUM / TEBEX
-- ════════════════════════════════════════════════════════════════
-- HOW THIS WORKS
-- ──────────────
--   1. Customer buys a "Premium Slot" package from your Tebex store.
--   2. Tebex runs a SERVER COMMAND on your fivem server (configured in
--      the package settings, see TEBEX_SETUP.md):
--          feron_mc_redeem_premium {"transid":"{transaction_id}","slots":1}
--   3. Our server-side handler catches that command and inserts the
--      transid into the `feron_multichar_keys` table (is_used = 0).
--   4. The customer enters the code in the multichar UI.
--   5. Server validates (exists + unused), increments their
--      `feron_multichar_slots.extra_slots` by the slot amount, and marks
--      the key consumed.
--
-- Same flow you used in dockerize_vip — just adapted for slot grants.
-- ──────────────────────────────────────────────────────────────────
Config.Tebex = {
    Link            = 'https://feron.tebex.io/',
    Enabled         = true,

    -- Name of the command Tebex will run on your server when a package is purchased.
    -- Must match what you configure in the Tebex package "Online Command" field.
    ServerCommand   = 'feron_mc_redeem_premium',

    -- Tebex transaction IDs follow a strict format:
    --   tbx-XXXXXXXXXXXXXX-XXXXXX
    --   (4-char prefix, 14 hex chars, dash, 6 hex chars — lowercase)
    -- We validate against this format BEFORE hitting the DB, so junk
    -- input is rejected instantly without burning a query.
    -- The DB lookup is still the source of truth.
    KeyPattern      = '^tbx%-[a-f0-9]+%-[a-f0-9]+$',
    KeyMinLen       = 20,   -- conservative lower bound (covers tbx-X+ -X+)
    KeyMaxLen       = 64,   -- conservative upper bound

    -- Mark redeemed keys as used (is_used=1) so they cannot be reused.
    -- Set false to delete the row instead.
    KeepUsedKeys    = true,
}
```

{% endtab %}
{% endtabs %}


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://feron-scripts.gitbook.io/welcome/scripts/feron-multicharacter/config/framework-slots.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
