Convert a QBCore script to QBox: qbx_core, ox_inventory, ox_target

How to port a QBCore script to QBox: the qbx_core compatibility layer, GetPlayer, ox_inventory items, ox_lib notify and progress, ox_target, and what usually breaks.

You have a script written for QBCore and a server running QBox. Sometimes it starts and works, sometimes the console fills with errors about a missing qb-inventory, qb-target or qb-menu. Here is the order to port it in, and the places where it usually breaks.

If you are still choosing a core, the same job from the other direction is in convert an ESX script to QBCore.

What QBox changes and what it keeps

QBox is built on qbx_core plus the Overextended resources: ox_lib, oxmysql, ox_inventory and ox_target. It keeps a compatibility layer, so exports['qb-core']:GetCoreObject() still answers and PlayerData has the same shape (citizenid, job, charinfo, metadata, money). That is why many scripts run untouched.

What it does not keep is the old QB utility resources. Where QBCore used qb-inventory, qb-target, qb-menu, qb-input and qb-progressbar, QBox expects ox_inventory, ox_target and ox_lib. Those are the parts you port.

The first step is making sure the core is started correctly:

cfg
ensure oxmysql
ensure ox_lib
ensure qbx_core
ensure ox_inventory
ensure ox_target
ensure my_script

Remove any leftover qb-core folder, since two cores conflict. See QBCore is nil: GetCoreObject if the object is missing.

Step 1: dependencies in the manifest

lua
fx_version 'cerulean'
game 'gta5'
lua54 'yes'

shared_script '@ox_lib/init.lua'

dependencies { 'qbx_core', 'ox_lib', 'ox_inventory', 'ox_target' }

@ox_lib/init.lua is what gives you the lib global. If it is not found, see ox_lib init.lua not found.

Step 2: getting the player

Both of these work on QBox. The first is the compatibility style, the second is the QBox way:

lua
-- QBCore style (still works through the compatibility layer)
local QBCore = exports['qb-core']:GetCoreObject()
local Player = QBCore.Functions.GetPlayer(source)

-- QBox style
local player = exports.qbx_core:GetPlayer(source)
if player then
    print(player.PlayerData.citizenid, player.PlayerData.job.name)
end

Both return nil for a player who is not loaded, so keep the nil check. To find a loaded player by citizen id, QBox also has GetPlayerByCitizenId. When you port, you can leave the QBCore calls in place and replace them one by one later.

Step 3: inventory

This is where most scripts break. qb-inventory and ox_inventory do not share item definitions or function names.

Task QBCore QBox with ox_inventory
Add an item Player.Functions.AddItem('water', 1) exports.ox_inventory:AddItem(source, 'water', 1)
Remove an item Player.Functions.RemoveItem('water', 1) exports.ox_inventory:RemoveItem(source, 'water', 1)
Count Player.Functions.GetItemByName('water').amount exports.ox_inventory:GetItemCount(source, 'water')
Metadata info table metadata table
lua
local src = source
local ok = exports.ox_inventory:AddItem(src, 'water', 2, { quality = 100 })
if not ok then
    lib.notify(src, { description = 'Your inventory is full', type = 'error' })
end

AddItem returns a falsy value when the item does not exist or cannot be carried, so check it. Item definitions move too. QBCore keeps them in qb-core/shared/items.lua, ox_inventory in ox_inventory/data/items.lua. Add every item the script uses there, in ox_inventory's format. How to do that is in add items to ox_inventory, and the QBCore format you are coming from is in add items to qb-inventory.

Usable items are registered differently as well: QBCore.Functions.CreateUseableItem becomes an export set in the item's definition in ox_inventory, which points at a function in your script. The QBCore calls may still work through the compatibility layer, so test an item before you rewrite it.

Step 4: notifications, progress and menus with ox_lib

ox_lib replaces the separate QB utility resources. The calls are close to what you know:

lua
-- QBCore
QBCore.Functions.Notify('Done', 'success', 5000)

-- ox_lib (client)
lib.notify({ title = 'Job', description = 'Done', type = 'success', duration = 5000 })
lua
-- QBCore: QBCore.Functions.Progressbar(...) with a callback
-- ox_lib: returns true when completed, false when cancelled
if lib.progressBar({
    duration = 5000,
    label = 'Repairing',
    useWhileDead = false,
    canCancel = true,
    disable = { car = true, move = true },
    anim = { dict = 'mini@repair', clip = 'fixing_a_ped' },
}) then
    print('finished')
else
    print('cancelled')
end

Menus move from qb-menu to lib.registerContext and lib.showContext, and input forms from qb-input to lib.inputDialog. Full examples are in notifications and progress bars on ESX, QBCore and ox_lib and ox_lib context menus.

Step 5: ox_target instead of qb-target

qb-target takes a name, coordinates and a table of options in its own layout. ox_target takes one table:

lua
-- qb-target
exports['qb-target']:AddBoxZone('my_zone', vector3(215.0, -810.0, 30.7), 1.5, 1.5, {
    name = 'my_zone', heading = 0, minZ = 29.7, maxZ = 32.7,
}, {
    options = { { event = 'my_script:client:open', icon = 'fas fa-box', label = 'Open', job = 'police' } },
    distance = 2.0,
})

-- ox_target
exports.ox_target:addBoxZone({
    coords = vector3(215.0, -810.0, 30.7),
    size = vector3(1.5, 1.5, 3.0),
    rotation = 0,
    options = {
        { name = 'my_zone_open', icon = 'fas fa-box', label = 'Open', groups = 'police',
          onSelect = function() TriggerEvent('my_script:client:open') end },
    },
})

Notice event becomes onSelect (or event still works for a client event), and job becomes groups. The details are in qb-target vs ox_target and ox_target zones.

What usually breaks

  • Missing item errors. The item exists in qb-core's shared file but not in ox_inventory's. Add it.
  • No such export for qb-inventory, qb-target or qb-menu. The script calls them directly: port those calls, or it will never run.
  • Callbacks. lib.callback is the QBox style. If a script still uses QBCore.Functions.CreateCallback, test it and move it to ox_lib if it fails: see server callbacks.
  • Jobs and gangs with grades. QBox has its own job and group data. Test every job check, especially on-duty and grade comparisons.
  • Mixed cores. A leftover qb-core resource started next to qbx_core.
  • Start order. Your script above ox_lib, ox_inventory or qbx_core in server.cfg.

Checklist

Symptom Fix
GetCoreObject is nil Start qbx_core first and remove any qb-core folder
No such export for qb-inventory Use exports.ox_inventory:AddItem(source, item, count)
Item not found Add it to ox_inventory/data/items.lua
qb-target calls fail Port to exports.ox_target:addBoxZone or addLocalEntity
lib is nil Add shared_script '@ox_lib/init.lua'
Job check always false Test PlayerData.job.name and grade on QBox

Quick answers

Do QBCore scripts work on QBox without changes?

Many do, because qbx_core keeps a compatibility layer for qb-core. Scripts that depend on qb-inventory, qb-target or qb-menu usually need work, since QBox uses ox_inventory, ox_target and ox_lib instead.

How do I get the player on QBox?

On the server, exports.qbx_core:GetPlayer(source) returns the player, with the same PlayerData table you know from QBCore.

Do I have to rewrite everything to move to QBox?

No. Start the script on QBox, read the errors, and replace the pieces that fail: inventory, target, menus and notifications. The rest, such as PlayerData, jobs and metadata, keeps its shape.

Scripts that skip this problem

Item Creator V2Create usable items with animations, props, effects and more β€” without writing code.View script β†’Shop CreatorBuild a shop in under a minute β€” owners, employees, vaults and robberies included.View script β†’Quest CreatorA visual editor for quests and NPC dialogues, built node by node in game.View script β†’

Keep reading