Oxide StudiosOxide Studios

Legacy Compatibility Guide

Documentation for migrating from qb-menu to oxide-menu.

Table of Contents


Overview

Oxide Menu supports the legacy qb-menu data format. This means you can use the same menu structure (headers, items, params) but you must update the export name.

What's Compatible

  1. Legacy data format (header, txt, params, etc.) works as-is
  2. Legacy exports (openMenu, closeMenu, showHeader) use the same function signatures
  3. Legacy events (qb-menu:client:menuClosed) still fire
  4. Action functions are preserved and executed correctly

What Must Change

You must change the export name from 'qb-menu' to 'oxide-menu' in your scripts.


Migration Requirements

Step 1: Update server.cfg

# server.cfg
# ensure qb-menu    -- Remove or comment out
ensure oxide-menu   -- Add this

Step 2: Update Export Names

Find and replace in your scripts:

-- Before
exports['qb-menu']:openMenu(...)
exports['qb-menu']:closeMenu()

-- After
exports['oxide-menu']:openMenu(...)
exports['oxide-menu']:closeMenu()

The menu data structure remains the same - only the export name changes.


Legacy Exports

These exports accept the same parameters as qb-menu:

openMenu

exports['oxide-menu']:openMenu(menuData, sort, skipFirst)
ParameterTypeDescription
menuDatatableArray of menu items
sortbooleanSort items alphabetically
skipFirstbooleanSkip first item when sorting (preserve header)

closeMenu

exports['oxide-menu']:closeMenu()

showHeader

Alias for openMenu:

exports['oxide-menu']:showHeader(menuData)

Legacy Item Format

The legacy qb-menu item format is fully supported:

{
    header = 'Menu Title',
    isMenuHeader = true,
}

Standard Item

{
    header = 'Item Label',
    txt = 'Description text',
    icon = 'fas fa-icon',
    disabled = false,
    hidden = false,
}

Item with Client Event

{
    header = 'Client Action',
    txt = 'Triggers client event',
    icon = 'fas fa-bolt',
    params = {
        event = 'my:clientEvent',
        args = { key = 'value' }
    }
}

Item with Server Event

{
    header = 'Server Action',
    txt = 'Triggers server event',
    icon = 'fas fa-server',
    params = {
        isServer = true,
        event = 'my:serverEvent',
        args = { key = 'value' }
    }
}

Item with Command

{
    header = 'Execute Command',
    txt = 'Runs a command',
    icon = 'fas fa-terminal',
    params = {
        isCommand = true,
        event = 'mycommand'
    }
}

Item with QBCore Command

{
    header = 'QBCore Command',
    txt = 'Runs QBCore command',
    icon = 'fas fa-terminal',
    params = {
        isQBCommand = true,
        event = 'giveitem',
        args = { item = 'water', amount = 1 }
    }
}

Item with Action Function

{
    header = 'Direct Action',
    txt = 'Calls a function',
    icon = 'fas fa-play',
    params = {
        isAction = true,
        event = function(args)
            print('Action called with:', json.encode(args))
        end,
        args = { custom = 'data' }
    }
}

-- Or simpler syntax:
{
    header = 'Simple Action',
    action = function()
        print('Action called!')
    end
}

Item with Persist (Keep Menu Open)

-- Item-level persist
{
    header = 'Buy Water',
    txt = 'Menu stays open',
    icon = 'fas fa-tint',
    persist = true,  -- Keep menu open after selection
    params = {
        isServer = true,
        event = 'shop:buyItem',
        args = { item = 'water' }
    }
}

-- Or persist in params
{
    header = 'Buy Bread',
    txt = 'Menu stays open',
    params = {
        isServer = true,
        event = 'shop:buyItem',
        args = { item = 'bread' },
        persist = true  -- Also works here
    }
}

Legacy Events

qb-menu:client:menuClosed

This event fires when a menu opened with the legacy API is closed:

AddEventHandler('qb-menu:client:menuClosed', function()
    print('Menu was closed')
end)

Note: The modern event oxide-menu:client:closed also fires for all menus.


Format Transformation

When you use openMenu, items are transformed to the modern format internally.

Transformation Rules

Legacy PropertyModern Property
headerlabel
txtdescription
textdescription
isMenuHeaderisHeader
iconicon
disableddisabled
hiddenhidden
persistpersist
params.eventevent or serverEvent
params.argsargs
params.isServerUses serverEvent
params.isCommandUses command
params.isQBCommandUses qbCommand
params.isActionStored in action lookup
params.persistpersist
action (function)Stored in action lookup

Example Transformation

Input (Legacy):

{
    header = 'Buy Item',
    txt = 'Purchase this item for $50',
    icon = 'fas fa-shopping-cart',
    params = {
        isServer = true,
        event = 'shop:buyItem',
        args = { item = 'water', price = 50 }
    }
}

Internal (Modern):

{
    label = 'Buy Item',
    description = 'Purchase this item for $50',
    icon = 'fas fa-shopping-cart',
    serverEvent = 'shop:buyItem',
    args = { item = 'water', price = 50 },
    _legacyIndex = 2
}

Action Functions

Functions can't be sent to the NUI (browser), so Oxide Menu handles them specially.

How Actions Work

  1. When transforming legacy items, functions are stored in a Lua table
  2. The item gets a _hasAction = true flag and _legacyIndex
  3. When selected, the Lua callback retrieves and executes the function
  4. The function table is cleared when the menu closes

Supported Action Patterns

-- Pattern 1: Direct action property
{
    header = 'Do Something',
    action = function()
        print('Hello!')
    end
}

-- Pattern 2: Action in params with isAction
{
    header = 'Do Something',
    params = {
        isAction = true,
        event = function(args)
            print('Args:', json.encode(args))
        end,
        args = { foo = 'bar' }
    }
}

Migration Tips

After changing export names, consider migrating to the modern API for additional features.

Why Migrate?

FeatureLegacy APIModern API
CheckboxesNoYes
SlidersNoYes
Text inputsNoYes
onSelect callbackNoYes
onClose callbackNoYes
Menu ID trackingNoYes
Submenus (registered)NoYes

Gradual Migration

You can use both APIs simultaneously:

-- Old code still works
exports['oxide-menu']:openMenu({
    { header = 'Old Menu', isMenuHeader = true },
    { header = 'Option 1', params = { event = 'old:event' } },
})

-- New code uses modern API
exports['oxide-menu']:open({
    id = 'new-menu',
    title = 'New Menu',
    items = {
        { label = 'Option 1', event = 'new:event' },
        { type = 'checkbox', label = 'Toggle', checked = false },
    },
    onSelect = function(item, index)
        print('Selected:', item.label)
    end
})

Quick Conversion Guide

Legacy:

exports['oxide-menu']:openMenu({
    {
        header = 'Shop',
        isMenuHeader = true,
    },
    {
        header = 'Buy Water',
        txt = '$5',
        icon = 'fas fa-tint',
        params = {
            isServer = true,
            event = 'shop:buy',
            args = { item = 'water' }
        }
    }
})

Modern:

exports['oxide-menu']:open({
    title = 'Shop',
    items = {
        {
            label = 'Buy Water',
            description = '$5',
            icon = 'fas fa-tint',
            serverEvent = 'shop:buy',
            args = { item = 'water' }
        }
    }
})

Compatibility Matrix

qb-menu FeatureOxide Menu Support
openMenu()Full
closeMenu()Full
showHeader()Full
header propertyFull
txt propertyFull
isMenuHeaderFull
iconFull (enhanced)
disabledFull
hiddenFull
persistFull (new feature)
params.eventFull
params.isServerFull
params.isCommandFull
params.isQBCommandFull
params.isActionFull
params.persistFull (new feature)
action functionFull
params.argsFull
Sort functionalityFull
qb-menu:client:menuClosedFull

Known Differences

  1. Visual Styling: Oxide Menu has a modern semi-transparent UI
  2. Animations: Menus animate in/out (configurable via Config.Animation)
  3. Search: Menus show a search bar (configurable via Config.Search)
  4. Keyboard: Arrow key navigation (configurable via Config.Keyboard.enabled)

These are additive features that don't break existing functionality. All can be configured or disabled in config.lua.


Troubleshooting Legacy Issues

Check if the legacy format is correct. The first item should typically be a header:

{
    header = 'Menu Title',
    isMenuHeader = true,  -- Required for header
}

Events not firing

Ensure your event names are correct and the receiving handlers are registered:

-- Client event handler
AddEventHandler('my:clientEvent', function(args)
    print('Received:', json.encode(args))
end)

-- Server event handler
RegisterNetEvent('my:serverEvent', function(args)
    print('Received:', json.encode(args))
end)

Action functions not working

Make sure the function is defined correctly:

-- Correct
action = function()
    print('Works!')
end

-- Incorrect (passing a string)
action = 'functionName'

Running alongside qb-menu

Running both resources simultaneously is not recommended as they serve the same purpose. Choose one and migrate fully.