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

# Custom Framework Integration

If your server uses a framework that isn't automatically detected (QBCore, ESX, QBox, etc.), you can integrate FB-Blackmarket with **any** framework by editing two files:

| File                  | Side   | Purpose                                   |
| --------------------- | ------ | ----------------------------------------- |
| `editable/client.lua` | Client | Player data, notifications, UI helpers    |
| `editable/server.lua` | Server | Money, inventory, metadata, notifications |

{% hint style="info" %}
These files are **not escrowed** — you have full access to modify them.
{% endhint %}

#### When Do You Need This?

* ✅ `Config.Framework = "auto"` handles **QBCore, QBox, ESX, ND\_Core, ox\_core, vRP** automatically
* ✅ If you use one of these, you **don't** need to touch editable files
* 🔧 Only edit these files if you set `Config.Framework = "custom"` or need to override default behavior

{% stepper %}
{% step %}

#### Set Framework to Custom

```lua
-- config.lua
Config.Framework = "custom"
```

{% endstep %}

{% step %}

#### Implement Client Functions

Edit `editable/client.lua` — all functions must be implemented:

<details>

<summary>Editable.GetPlayerData() — Return the player data table</summary>

```lua
function Editable.GetPlayerData()
    -- Example: Custom framework
    return exports['my-core']:GetPlayerData()

    -- Example: Already implemented fallbacks:
    --   QBCore: exports['qb-core']:GetCoreObject().Functions.GetPlayerData()
    --   ESX:    exports['es_extended']:getSharedObject().GetPlayerData()
end
```

</details>

<details>

<summary>Editable.IsPlayerLoaded() — Check if player data is ready</summary>

```lua
function Editable.IsPlayerLoaded()
    -- Return true once your framework has loaded the player
    return exports['my-core']:IsPlayerReady()
end
```

</details>

<details>

<summary>Editable.Notify(msg, type) — Client-side notifications</summary>

The default implementation auto-detects QBCore → ESX → ox\_lib → chat. To use a custom system, replace OPTION 1 with your preferred script:

```lua
function Editable.Notify(msg, type)
    if Config.Notifications and Config.Notifications.Enabled == false then return end
    local nType = type or 'primary'

    -- Choose ONE of these:
    exports['okokNotify']:Alert('Blackmarket', msg, 5000, nType)
    -- exports['wasabi_notify']:notify('Blackmarket', msg, 5000, nType)
    -- exports['mythic_notify']:DoHudText(nType, msg)
    -- exports['codem-notification']:SendNotification(nType:upper(), msg)
    -- exports['bulletin']:Send(msg, 5000, nType)
    -- exports['t-notify']:Custom({ title = 'Blackmarket', message = msg, duration = 5000, style = nType })
    -- lib.notify({ title = 'Blackmarket', description = msg, type = nType, duration = 5000 })
end
```

</details>

<details>

<summary>Editable.AddTargetEntity(entity, options, distance) — Custom target system</summary>

```lua
function Editable.AddTargetEntity(entity, options, distance)
    -- options format: { { label = "Open Blackmarket", icon = "fas fa-skull", action = function() end } }

    -- Example: Custom target
    exports['my-target']:AddEntity(entity, {
        label = options[1].label,
        icon = options[1].icon,
        distance = distance,
        onSelect = options[1].action
    })
end

function Editable.RemoveTargetEntity(entity)
    exports['my-target']:RemoveEntity(entity)
end
```

</details>

<details>

<summary>Editable.ProgressBar(duration, label) — Progress bar wrapper</summary>

Default implementation tries ox\_lib → QBCore → simple Wait. Override for custom:

```lua
function Editable.ProgressBar(duration, label)
    -- Example: Custom progress bar
    local success = exports['my-progressbar']:Start({
        duration = duration,
        label = label or "Processing...",
        canCancel = true
    })
    return success
end
```

</details>

<details>

<summary>Editable.Dispatch(coords, msg) — Police alert system</summary>

```lua
function Editable.Dispatch(coords, msg)
    -- Example: ps-dispatch
    exports['ps-dispatch']:SuspiciousActivity()

    -- Example: cd_dispatch
    -- exports['cd_dispatch']:Alert({ coords = coords, message = msg })

    -- Example: qs-dispatch
    -- exports['qs-dispatch']:Alert({ coords = coords, message = msg, job = 'police' })
end
```

</details>

<details>

<summary>Editable.GetVehicleProperties(vehicle) — Get vehicle mods table</summary>

```lua
function Editable.GetVehicleProperties(vehicle)
    -- Default: auto-detects QBCore/ESX
    -- Override for custom:
    return exports['my-vehicles']:GetProperties(vehicle)
end
```

</details>
{% endstep %}

{% step %}

#### Implement Server Functions

Edit `editable/server.lua` — these handle the core economy:

<details>

<summary>Editable.GetPlayer(source) — Get player object</summary>

```lua
function Editable.GetPlayer(source)
    return exports['my-core']:GetPlayer(source)
end
```

</details>

<details>

<summary>Editable.GetIdentifier(source) — Get unique player ID</summary>

```lua
function Editable.GetIdentifier(source)
    -- Default: extracts license from GetPlayerIdentifiers()
    -- Override if your framework uses a custom identifier:
    return exports['my-core']:GetPlayer(source).identifier
end
```

</details>

<details>

<summary>Editable.GetPlayerName(source) — Get first &#x26; last name</summary>

```lua
function Editable.GetPlayerName(source)
    local player = exports['my-core']:GetPlayer(source)
    return player.firstname, player.lastname
end
```

</details>

<details>

<summary>Money Functions — GetMoney / AddMoney / RemoveMoney</summary>

```lua
function Editable.GetMoney(source, type)
    local player = exports['my-core']:GetPlayer(source)
    if type == 'cash' then return player.cash end
    if type == 'bank' then return player.bank end
    if type == 'black' then return player.dirty_money end
    return 0
end

function Editable.AddMoney(source, type, amount)
    return exports['my-core']:AddMoney(source, type, amount)
end

function Editable.RemoveMoney(source, type, amount)
    local current = Editable.GetMoney(source, type)
    if current >= amount then
        exports['my-core']:RemoveMoney(source, type, amount)
        return true
    end
    return false
end
```

</details>

<details>

<summary>Inventory Functions — AddItem / RemoveItem / GetItemCount / CanCarryItem</summary>

```lua
function Editable.AddItem(source, item, amount)
    return exports['my-inventory']:AddItem(source, item, amount)
end

function Editable.RemoveItem(source, item, amount)
    return exports['my-inventory']:RemoveItem(source, item, amount)
end

function Editable.GetItemCount(source, item)
    return exports['my-inventory']:GetItemCount(source, item) or 0
end

function Editable.GetItemLabel(item)
    return exports['my-inventory']:GetItemLabel(item)
end

function Editable.CanCarryItem(source, item, amount)
    return exports['my-inventory']:CanCarryItem(source, item, amount)
end
```

</details>

<details>

<summary>Editable.Notify(source, msg, type) — Server → Client notification</summary>

```lua
function Editable.Notify(source, msg, type)
    if Config.Notifications and Config.Notifications.Enabled == false then return end
    local nType = type or 'primary'

    -- Choose ONE:
    TriggerClientEvent('okokNotify:Alert', source, 'Blackmarket', msg, 5000, nType)
    -- TriggerClientEvent('wasabi_notify:notify', source, 'Blackmarket', msg, 5000, nType)
    -- TriggerClientEvent('QBCore:Notify', source, msg, nType, 5000)
    -- TriggerClientEvent('esx:showNotification', source, msg)
    -- TriggerClientEvent('ox_lib:notify', source, { title = 'Blackmarket', description = msg, type = nType })
end
```

</details>

<details>

<summary>Metadata Functions — GetMetaData / SetMetaData</summary>

```lua
function Editable.GetMetaData(source, key)
    -- Used to store/retrieve blackmarket level, XP, tasks
    return exports['my-core']:GetMetaData(source, key)
end

function Editable.SetMetaData(source, key, value)
    exports['my-core']:SetMetaData(source, key, value)
end
```

{% hint style="danger" %}
Metadata is critical — this is where player level/XP data is stored. If your framework doesn't support metadata, consider using the database tables (`player_blackmarket`) instead.
{% endhint %}

</details>
{% endstep %}
{% endstepper %}

#### Quick Reference

**Client Functions (`editable/client.lua`):**

| Function                                 | Required | Default Behavior                     |
| ---------------------------------------- | -------- | ------------------------------------ |
| `GetPlayerData()`                        | ✅        | Returns empty table                  |
| `IsPlayerLoaded()`                       | ✅        | Returns `true`                       |
| `Notify(msg, type)`                      | ✅        | Auto-detects QBCore/ESX/ox\_lib/chat |
| `DrawText3D(coords, text)`               | ⚡        | Native 3D text rendering             |
| `AddTargetEntity(entity, options, dist)` | 🔧       | Empty (must implement)               |
| `RemoveTargetEntity(entity)`             | 🔧       | Empty (must implement)               |
| `ProgressBar(duration, label)`           | ⚡        | Auto-detects ox\_lib/QBCore/Wait     |
| `Dispatch(coords, msg)`                  | 🔧       | Empty (must implement)               |
| `GetVehicleProperties(vehicle)`          | ⚡        | Auto-detects QBCore/ESX              |

**Server Functions (`editable/server.lua`):**

| Function                             | Required | Default Behavior                     |
| ------------------------------------ | -------- | ------------------------------------ |
| `GetPlayer(source)`                  | ✅        | Returns `nil`                        |
| `GetIdentifier(source)`              | ⚡        | Extracts `license` from identifiers  |
| `GetPlayerName(source)`              | ⚡        | Returns Steam name                   |
| `GetMoney(source, type)`             | ✅        | Returns `0`                          |
| `AddMoney(source, type, amount)`     | ✅        | Returns `true` (no-op)               |
| `RemoveMoney(source, type, amount)`  | ✅        | Returns `false`                      |
| `AddItem(source, item, amount)`      | ✅        | Returns `true` (no-op)               |
| `RemoveItem(source, item, amount)`   | ✅        | Returns `true` (no-op)               |
| `GetItemCount(source, item)`         | ✅        | Returns `0`                          |
| `GetItemLabel(item)`                 | ⚡        | Returns `nil`                        |
| `CanCarryItem(source, item, amount)` | ⚡        | Returns `true`                       |
| `Notify(source, msg, type)`          | ✅        | Auto-detects QBCore/ESX/ox\_lib/chat |
| `GetMetaData(source, key)`           | ✅        | Returns `nil`                        |
| `SetMetaData(source, key, value)`    | ✅        | No-op                                |

{% hint style="info" %}
✅ = Must implement for purchases to work · ⚡ = Has working default · 🔧 = Only needed for specific featuresFor custom framework integration, edit files in `editable/`:
{% endhint %}
