> For the complete documentation index, see [llms.txt](https://izaap-studios.gitbook.io/izaap-scripts/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://izaap-studios.gitbook.io/izaap-scripts/garages-advanced/script-integrations/lb-phone.md).

# lb-phone

***

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

Below is an **ESX-ready** drop-in replacement for **lb-phone**:

Path: `lb-phone/server/custom/frameworks/esx/vehicles.lua`\
Replace only **GetPlayerVehicles** and **GetVehicle** with the versions below.

This version is tailored for **izaap\_garage** using your **owned\_vehicles** schema + your optional impound columns, and it uses your exports to resolve **garage/impound labels**.

> Assumptions (matches your SQL + server logic):
>
> * ESX table: `owned_vehicles`
> * Ownership column: `owner`
> * Plate column: `plate`
> * Stored column: `stored` (fallback `state`)
> * Garage column: `parking` (fallback `garage`)
> * Vehicle JSON: `mods` (fallback `vehicle`)
> * Optional impound columns: `impounded`, `impound_id`, `impound_label`, `impound_reason`, `impound_release_at`, `impound_fee`, `impound_fee_before`, `impound_fee_after`, etc.

```lua
-- izaap_garage integration for lb-phone (ESX)
-- Replace GetPlayerVehicles + GetVehicle with this.

local function Trim(s)
    s = tostring(s or "")
    return (s:gsub("^%s*(.-)%s*$", "%1"))
end

local function SafeDecode(v)
    if type(v) == "table" then return v end
    if type(v) ~= "string" then return {} end
    local s = Trim(v)
    if s == "" then return {} end
    local first = s:sub(1, 1)
    if first ~= "{" and first ~= "[" then return {} end
    local ok, obj = pcall(json.decode, s)
    return (ok and type(obj) == "table") and obj or {}
end

local function HasColumn(tableName, columnName)
    local row = MySQL.single.await([[
        SELECT 1 as ok
        FROM information_schema.COLUMNS
        WHERE TABLE_SCHEMA = DATABASE()
          AND TABLE_NAME = ?
          AND COLUMN_NAME = ?
        LIMIT 1
    ]], { tableName, columnName })
    return row ~= nil
end

local ESX_COLS = nil
local function EnsureCols()
    if ESX_COLS then return ESX_COLS end

    ESX_COLS = {
        stored     = HasColumn("owned_vehicles", "stored") and "stored" or (HasColumn("owned_vehicles", "state") and "state" or nil),
        parking    = HasColumn("owned_vehicles", "parking") and "parking" or (HasColumn("owned_vehicles", "garage") and "garage" or nil),
        modsJson   = HasColumn("owned_vehicles", "mods") and "mods" or (HasColumn("owned_vehicles", "vehicle") and "vehicle" or nil),
        vehName    = HasColumn("owned_vehicles", "vehicle") and "vehicle" or nil,

        impounded  = HasColumn("owned_vehicles", "impounded") and "impounded" or nil,
        impound_id = HasColumn("owned_vehicles", "impound_id") and "impound_id" or nil,
        imp_label  = HasColumn("owned_vehicles", "impound_label") and "impound_label" or nil,
        imp_reason = HasColumn("owned_vehicles", "impound_reason") and "impound_reason" or nil,
        imp_rel    = HasColumn("owned_vehicles", "impound_release_at") and "impound_release_at" or nil,
        imp_fee    = HasColumn("owned_vehicles", "impound_fee") and "impound_fee" or nil,
        imp_fee_b  = HasColumn("owned_vehicles", "impound_fee_before") and "impound_fee_before" or nil,
        imp_fee_a  = HasColumn("owned_vehicles", "impound_fee_after") and "impound_fee_after" or nil,
    }

    return ESX_COLS
end

local function GetGarageLabelFromRow(row)
    local cols = EnsureCols()
    if GetResourceState("izaap_garage") ~= "started" then
        return row[cols.parking] or "Garage"
    end

    local g = row[cols.parking]
    if not g or Trim(g) == "" then return "Garage" end

    -- Your export accepts index (1-based) OR label; returning label is fine
    local data = exports["izaap_garage"]:getGarageByIndex(g)
    return (data and data.label) or (data and data.Label) or g
end

local function GetImpoundLabelFromRow(row)
    local cols = EnsureCols()
    if GetResourceState("izaap_garage") ~= "started" then
        return "Impound"
    end

    local label = cols.imp_label and row[cols.imp_label] or nil
    if label and Trim(label) ~= "" then return label end

    local id = cols.impound_id and tonumber(row[cols.impound_id]) or nil
    if id and id > 0 then
        local imp = exports["izaap_garage"]:getImpoundByIndex(id)
        return (imp and imp.label) or (imp and imp.Label) or "Impound"
    end

    return "Impound"
end

local function ReadStatsFromProps(props)
    props = type(props) == "table" and props or {}

    local fuel = tonumber(props.izaap_fuel or props.fuelLevel or props.fuel) -- 0-100
    local eng  = tonumber(props.izaap_engine or props.engineHealth or props.engine) -- 0-1000
    local body = tonumber(props.izaap_body or props.bodyHealth or props.body) -- 0-1000

    local stats = {}

    if fuel then stats.fuel = math.floor(fuel + 0.5) end
    if eng  then stats.engine = math.floor((eng / 10) + 0.5) end
    if body then stats.body   = math.floor((body / 10) + 0.5) end

    return stats
end

function GetPlayerVehicles(source)
    local cols = EnsureCols()

    local vehicles = MySQL.query.await("SELECT * FROM owned_vehicles WHERE owner = ?", { GetIdentifier(source) }) or {}
    local toSend = {}

    for i = 1, #vehicles do
        local v = vehicles[i] or {}

        local storedRaw = cols.stored and v[cols.stored] or 0
        local storedNum = tonumber(storedRaw) or 0

        local impounded = false
        if cols.impounded then
            impounded = (tonumber(v[cols.impounded]) or 0) == 1
        else
            -- fallback legacy: some schemas use stored==2 for impound
            impounded = storedNum == 2
        end

        local stored = (storedNum == 1) and true or false

        local location = stored and GetGarageLabelFromRow(v) or "out"
        if impounded then
            location = GetImpoundLabelFromRow(v)
        end

        local props = SafeDecode(cols.modsJson and v[cols.modsJson] or nil)
        local stats = ReadStatsFromProps(props)

        local model = props.model
        if model == nil and cols.vehName then
            -- some ESX schemas store spawn name in `vehicle` as string
            local maybe = v[cols.vehName]
            if type(maybe) == "string" and Trim(maybe) ~= "" and not tonumber(maybe) then
                model = maybe
            end
        end

        local impoundReason = nil
        if impounded then
            local reason = cols.imp_reason and v[cols.imp_reason] or nil
            local retrievable = cols.imp_rel and tonumber(v[cols.imp_rel]) or nil

            local price =
                (cols.imp_fee and tonumber(v[cols.imp_fee])) or
                (cols.imp_fee_b and tonumber(v[cols.imp_fee_b])) or
                (cols.imp_fee_a and tonumber(v[cols.imp_fee_a])) or
                nil

            impoundReason = {
                reason = (type(reason) == "string" and Trim(reason) ~= "" and reason) or nil,
                retrievable = retrievable,
                price = price
            }
        end

        toSend[#toSend+1] = {
            plate = v.plate,
            type = nil,
            location = location,
            impounded = impounded,
            statistics = stats,
            impoundReason = impoundReason,
            model = model
        }
    end

    return toSend
end


---------------------------------------------------------------------------------------------

function GetVehicle(source, plate)
    local cols = EnsureCols()

    plate = Trim(plate or "")
    if plate == "" then return end

    -- only allow taking out if stored=1 and not impounded
    local storedCol = cols.stored or "stored"
    local storedCheck = ("`%s` = 1"):format(storedCol)

    if cols.impounded then
        storedCheck = storedCheck .. " AND `impounded` = 0"
    end

    local vehicle = MySQL.single.await(
        ("SELECT * FROM owned_vehicles WHERE owner = ? AND plate = ? AND %s LIMIT 1"):format(storedCheck),
        { GetIdentifier(source), plate }
    )

    if not vehicle then
        return
    end

    -- mark as out
    MySQL.update(
        ("UPDATE owned_vehicles SET `%s` = 0 WHERE plate = ?"):format(storedCol),
        { plate }
    )

    -- return model for phone usage
    local props = SafeDecode(cols.modsJson and vehicle[cols.modsJson] or nil)
    vehicle.model = props.model

    return vehicle
end
```

{% endtab %}

{% tab title="QBCORE/QBOX" %}

Below is the **QB-Core / QBX** version tailored for **izaap\_garage**.

Path:

* `lb-phone/server/custom/frameworks/qbcore/vehicles.lua`
* OR `lb-phone/server/custom/frameworks/qbox/vehicles.lua`

Replace only **GetPlayerVehicles** and **GetVehicle** with this.

It uses your **player\_vehicles** schema (citizenid/plate/vehicle/hash/mods/fuel/engine/body/state/garage) and your optional impound columns (impounded/impound\_id/impound\_label/impound\_reason/impound\_release\_at/impound\_fee...).

***

```lua
-- izaap_garage integration for lb-phone (QB-Core / QBX)
-- Replace GetPlayerVehicles + GetVehicle with this.

local function Trim(s)
    s = tostring(s or "")
    return (s:gsub("^%s*(.-)%s*$", "%1"))
end

local function SafeDecode(v)
    if type(v) == "table" then return v end
    if type(v) ~= "string" then return {} end
    local s = Trim(v)
    if s == "" then return {} end
    local first = s:sub(1, 1)
    if first ~= "{" and first ~= "[" then return {} end
    local ok, obj = pcall(json.decode, s)
    return (ok and type(obj) == "table") and obj or {}
end

local function HasColumn(tableName, columnName)
    local row = MySQL.single.await([[
        SELECT 1 as ok
        FROM information_schema.COLUMNS
        WHERE TABLE_SCHEMA = DATABASE()
          AND TABLE_NAME = ?
          AND COLUMN_NAME = ?
        LIMIT 1
    ]], { tableName, columnName })
    return row ~= nil
end

local QB_COLS = nil
local function EnsureCols()
    if QB_COLS then return QB_COLS end

    QB_COLS = {
        state   = HasColumn("player_vehicles", "state") and "state" or (HasColumn("player_vehicles", "stored") and "stored" or "state"),
        garage  = HasColumn("player_vehicles", "garage") and "garage" or (HasColumn("player_vehicles", "parking") and "parking" or "garage"),
        mods    = HasColumn("player_vehicles", "mods") and "mods" or (HasColumn("player_vehicles", "props") and "props" or "mods"),
        hash    = HasColumn("player_vehicles", "hash") and "hash" or nil,
        fuel    = HasColumn("player_vehicles", "fuel") and "fuel" or nil,
        engine  = HasColumn("player_vehicles", "engine") and "engine" or nil,
        body    = HasColumn("player_vehicles", "body") and "body" or nil,

        impounded  = HasColumn("player_vehicles", "impounded") and "impounded" or nil,
        impound_id = HasColumn("player_vehicles", "impound_id") and "impound_id" or nil,
        imp_label  = HasColumn("player_vehicles", "impound_label") and "impound_label" or nil,
        imp_reason = HasColumn("player_vehicles", "impound_reason") and "impound_reason" or nil,
        imp_rel    = HasColumn("player_vehicles", "impound_release_at") and "impound_release_at" or nil,
        imp_fee    = HasColumn("player_vehicles", "impound_fee") and "impound_fee" or nil,
        imp_fee_b  = HasColumn("player_vehicles", "impound_fee_before") and "impound_fee_before" or nil,
        imp_fee_a  = HasColumn("player_vehicles", "impound_fee_after") and "impound_fee_after" or nil,
    }

    return QB_COLS
end

local function GetGarageLabelFromRow(row)
    local cols = EnsureCols()

    local g = row[cols.garage]
    if not g or Trim(g) == "" then return "Garage" end

    if GetResourceState("izaap_garage") ~= "started" then
        return g
    end

    local data = exports["izaap_garage"]:getGarageByIndex(g)
    return (data and data.label) or (data and data.Label) or g
end

local function GetImpoundLabelFromRow(row)
    local cols = EnsureCols()

    if GetResourceState("izaap_garage") ~= "started" then
        return "Impound"
    end

    local label = cols.imp_label and row[cols.imp_label] or nil
    if label and Trim(label) ~= "" then return label end

    local id = cols.impound_id and tonumber(row[cols.impound_id]) or nil
    if id and id > 0 then
        local imp = exports["izaap_garage"]:getImpoundByIndex(id)
        return (imp and imp.label) or (imp and imp.Label) or "Impound"
    end

    return "Impound"
end

local function BuildImpoundReason(row)
    local cols = EnsureCols()

    local reason = cols.imp_reason and row[cols.imp_reason] or nil
    local retrievable = cols.imp_rel and tonumber(row[cols.imp_rel]) or nil

    local price =
        (cols.imp_fee and tonumber(row[cols.imp_fee])) or
        (cols.imp_fee_b and tonumber(row[cols.imp_fee_b])) or
        (cols.imp_fee_a and tonumber(row[cols.imp_fee_a])) or
        nil

    return {
        reason = (type(reason) == "string" and Trim(reason) ~= "" and reason) or nil,
        retrievable = retrievable,
        price = price
    }
end

function GetPlayerVehicles(source)
    local cols = EnsureCols()

    local toSend = {}
    local vehicles = MySQL.query.await(
        "SELECT * FROM player_vehicles WHERE citizenid = ?",
        { GetIdentifier(source) }
    ) or {}

    for i = 1, #vehicles do
        local v = vehicles[i] or {}

        local state = tonumber(v[cols.state] or 0) or 0

        local impounded = false
        local impoundReason = nil

        if cols.impounded then
            impounded = (tonumber(v[cols.impounded]) or 0) == 1
        else
            impounded = (state == 2)
        end

        local location = "out"
        if impounded then
            location = GetImpoundLabelFromRow(v)
            impoundReason = BuildImpoundReason(v)
        else
            if state == 1 or state == true then
                location = GetGarageLabelFromRow(v)
            else
                location = "out"
            end
        end

        local hash = cols.hash and tonumber(v[cols.hash]) or nil

        local category = "car"
        if QB and QB.Shared and QB.Shared.Vehicles and hash then
            local shared = QB.Shared.Vehicles[hash]
            if shared and shared.category then
                category = shared.category
            end
        end

        local stats = {
            engine = math.floor(((tonumber(v[cols.engine]) or 1000) / 10) + 0.5),
            body   = math.floor(((tonumber(v[cols.body]) or 1000) / 10) + 0.5),
            fuel   = math.floor((tonumber(v[cols.fuel]) or 100) + 0.5)
        }

        toSend[#toSend+1] = {
            plate = v.plate,
            type = category,
            location = location,
            impounded = impounded,
            statistics = stats,
            impoundReason = impoundReason,
            model = hash
        }
    end

    return toSend
end

-----------------------------------------------------------------------------

function GetVehicle(source, plate)
    local cols = EnsureCols()

    plate = Trim(plate or "")
    if plate == "" then return end

    -- only allow taking out if state=1 and not impounded (if column exists)
    local where = ("citizenid = ? AND plate = ? AND `%s` = 1"):format(cols.state)
    if cols.impounded then
        where = where .. " AND `impounded` = 0"
    end

    local sql = ("SELECT plate, `%s` as mods, `%s` as hash, `%s` as fuel FROM player_vehicles WHERE %s LIMIT 1"):format(
        cols.mods,
        cols.hash or "hash",
        cols.fuel or "fuel",
        where
    )

    local vehicle = MySQL.single.await(sql, { GetIdentifier(source), plate })
    if not vehicle then
        return
    end

    MySQL.update(
        ("UPDATE player_vehicles SET `%s` = 0 WHERE plate = ?"):format(cols.state),
        { plate }
    )

    vehicle.model = tonumber(vehicle.hash)
    return vehicle
end
```

#### Notes (important)

* This version **does not** depend on other garage resources (cd/jg/op/etc). It’s purpose-built for **izaap\_garage**.
* If your phone expects a different “type” mapping (car/air/sea), tell me what you store in QB vehicles (category/vehicleType) and I’ll map it cleanly.
* If your QB shared table is `QBCore.Shared.Vehicles` instead of `QB.Shared.Vehicles`, replace `QB` with `QBCore` in the category resolver.
  {% endtab %}
  {% endtabs %}
