Oxide StudiosOxide Studios

Exports & API Reference

Complete API documentation for Oxide Menu.

Table of Contents


Overview

Oxide Menu provides two APIs:

  1. Modern API - New, feature-rich API with callbacks and more options
  2. Legacy API - Supports qb-menu data format for easier migration

Both APIs can be used simultaneously. The legacy API internally converts to modern format.

Note: All exports use 'oxide-menu' as the resource name, not 'qb-menu'.


Modern API

open

Opens a menu with the specified configuration.

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

Parameters

NameTypeRequiredDescription
menuDatatableYesMenu configuration table
PropertyTypeDefaultDescription
idstringnilUnique menu identifier
titlestringnilMenu header title
subtitlestringnilSubtitle below title
positionstringConfig.Position'left', 'center', 'right'
searchablebooleantrueEnable search bar
persistbooleannilKeep menu open after item selection
itemstableArray of menu items
onSelectfunctionnilCallback when item selected
onClosefunctionnilCallback when menu closed
onRefreshfunctionnilCallback to rebuild items after selection (persist menus)

Returns

TypeDescription
booleantrue if menu opened successfully

Example

exports['oxide-menu']:open({
    id = 'vehicle-menu',
    title = 'Vehicle Options',
    subtitle = 'Manage your vehicle',
    position = 'right',
    items = {
        { label = 'Lock/Unlock', icon = 'fas fa-lock', event = 'vehicle:toggleLock' },
        { label = 'Engine', icon = 'fas fa-power-off', event = 'vehicle:toggleEngine' },
        { type = 'divider' },
        { label = 'Trunk', icon = 'fas fa-box-open', event = 'vehicle:openTrunk' },
    },
    onSelect = function(item, index)
        print('Selected item:', item.label, 'at index:', index)
    end,
    onClose = function()
        print('Vehicle menu closed')
    end
})

close

Closes the currently open menu or a specific menu by ID.

exports['oxide-menu']:close(menuId)

Parameters

NameTypeRequiredDescription
menuIdstringNoOptional menu ID to close

Example

-- Close any open menu
exports['oxide-menu']:close()

-- Close specific menu
exports['oxide-menu']:close('vehicle-menu')

isOpen

Checks if a menu is currently open.

local open = exports['oxide-menu']:isOpen(menuId)

Parameters

NameTypeRequiredDescription
menuIdstringNoOptional menu ID to check

Returns

TypeDescription
booleantrue if menu (or specific menu) is open

Example

-- Check if any menu is open
if exports['oxide-menu']:isOpen() then
    print('A menu is currently open')
end

-- Check specific menu
if exports['oxide-menu']:isOpen('vehicle-menu') then
    print('Vehicle menu is open')
end

register

Pre-registers a menu for later use with submenu references.

exports['oxide-menu']:register(menuId, menuData)

Parameters

NameTypeRequiredDescription
menuIdstringYesUnique menu identifier
menuDatatableYesMenu configuration table

Returns

TypeDescription
booleantrue if registered successfully

Example

-- Register a submenu
exports['oxide-menu']:register('settings-audio', {
    title = 'Audio Settings',
    items = {
        { type = 'slider', label = 'Master Volume', min = 0, max = 100, value = 80 },
        { type = 'slider', label = 'Music', min = 0, max = 100, value = 50 },
        { type = 'slider', label = 'SFX', min = 0, max = 100, value = 70 },
    }
})

-- Main menu with submenu reference
exports['oxide-menu']:open({
    title = 'Settings',
    items = {
        { label = 'Audio', icon = 'fas fa-volume-up', submenu = 'settings-audio' },
        { label = 'Video', icon = 'fas fa-tv', submenu = 'settings-video' },
    }
})

update

Updates the currently open menu with new data. Useful for refreshing items after state changes.

exports['oxide-menu']:update(data)

Parameters

NameTypeRequiredDescription
datatableYesUpdate data (items, title, subtitle)

Update Data Properties

PropertyTypeDescription
itemstableNew items array (replaces all items)
titlestringNew menu title
subtitlestringNew menu subtitle

Returns

TypeDescription
booleantrue if update was successful

Example

-- Update all items
exports['oxide-menu']:update({
    items = {
        { label = 'Water', description = 'Stock: 3' },
        { label = 'Bread', description = 'SOLD OUT', disabled = true },
    }
})

-- Update title only
exports['oxide-menu']:update({
    subtitle = 'Updated: ' .. os.date('%H:%M:%S')
})

updateItem

Updates a single item in the currently open menu by index.

exports['oxide-menu']:updateItem(index, itemData)

Parameters

NameTypeRequiredDescription
indexnumberYes1-based item index
itemDatatableYesProperties to update (merged with existing)

Returns

TypeDescription
booleantrue if update was successful

Example

-- Update item description
exports['oxide-menu']:updateItem(2, {
    description = 'Stock: 4'
})

-- Disable an item
exports['oxide-menu']:updateItem(3, {
    label = 'Bandage (SOLD OUT)',
    disabled = true
})

-- Update multiple properties
exports['oxide-menu']:updateItem(1, {
    label = 'Water (On Sale!)',
    description = '$1.50 | Stock: 10',
    icon = 'fas fa-tag'
})

refresh

Re-renders the current menu with its existing data. Useful after modifying currentMenu directly.

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

Returns

TypeDescription
booleantrue if refresh was successful

Legacy API

These exports accept the qb-menu data format for easier migration.

openMenu

Opens a menu using the qb-menu data format.

exports['oxide-menu']:openMenu(menuData, sort, skipFirst)

Parameters

NameTypeRequiredDescription
menuDatatableYesArray of legacy menu items
sortbooleanNoSort items alphabetically
skipFirstbooleanNoSkip first item when sorting

Legacy Item Format

PropertyTypeDescription
headerstringItem title/label
txtstringDescription text
iconstringFont Awesome icon class
isMenuHeaderbooleanMakes item a non-clickable header
disabledbooleanDisables the item
hiddenbooleanHides the item
paramstableAction parameters

params Table

PropertyTypeDescription
eventstring/functionEvent name or function
argsanyArguments to pass
isServerbooleanTrigger as server event
isCommandbooleanExecute as command
isQBCommandbooleanExecute as QBCore command
isActionbooleanEvent is a function

Example

exports['oxide-menu']:openMenu({
    {
        header = 'Shop Menu',
        isMenuHeader = true,
    },
    {
        header = 'Buy Water',
        txt = '$5 - Restores thirst',
        icon = 'fas fa-tint',
        params = {
            isServer = true,
            event = 'shop:buyItem',
            args = { item = 'water', price = 5 }
        }
    },
    {
        header = 'Buy Sandwich',
        txt = '$10 - Restores hunger',
        icon = 'fas fa-hamburger',
        params = {
            isServer = true,
            event = 'shop:buyItem',
            args = { item = 'sandwich', price = 10 }
        }
    },
})

closeMenu

Closes the currently open menu.

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

Example

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

showHeader

Alias for openMenu. Opens a menu using legacy format.

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

Modern Format

{
    id = 'menu-id',           -- Optional: Unique identifier
    title = 'Menu Title',     -- Optional: Header text
    subtitle = 'Subtitle',    -- Optional: Subheader text
    position = 'right',       -- Optional: left/center/right
    searchable = true,        -- Optional: Show search bar
    items = { ... },          -- Required: Array of items
    onSelect = function(item, index) end,  -- Optional: Selection callback
    onClose = function() end,              -- Optional: Close callback
}

Legacy Format

{
    {
        header = 'Title',
        isMenuHeader = true,
    },
    {
        header = 'Item Label',
        txt = 'Description',
        icon = 'fas fa-icon',
        params = { ... }
    },
    -- More items...
}

Item Types

Button (Default)

Standard clickable menu item.

{
    label = 'Button Text',
    description = 'Optional description',
    icon = 'fas fa-star',
    disabled = false,
    persist = false,  -- Keep menu open after selection

    -- Actions (choose one):
    event = 'client:eventName',
    serverEvent = 'server:eventName',
    command = 'commandName',
    qbCommand = 'qbCommandName',
    submenu = 'registered-menu-id',

    -- Arguments for events
    args = { key = 'value' },
}

Non-clickable section header.

{
    label = 'SECTION TITLE',
    isHeader = true,
}

Divider

Visual separator line.

{
    type = 'divider',
}

Checkbox

Toggle switch with boolean state.

{
    type = 'checkbox',
    label = 'Enable Feature',
    description = 'Turn this on or off',
    checked = false,  -- Initial state
}

Listen for changes:

AddEventHandler('oxide-menu:client:checkboxChange', function(index, checked, item)
    print(item.label .. ' is now ' .. (checked and 'ON' or 'OFF'))
end)

Slider

Range input with numeric value.

{
    type = 'slider',
    label = 'Volume',
    min = 0,
    max = 100,
    value = 50,   -- Initial value
    step = 5,     -- Increment amount
}

Listen for changes:

AddEventHandler('oxide-menu:client:sliderChange', function(index, value, item)
    print(item.label .. ' set to ' .. value)
end)

Input

Text input field.

{
    type = 'input',
    label = 'Player Name',
    placeholder = 'Enter name...',
    value = '',  -- Initial value
}

Listen for submit (Enter key):

AddEventHandler('oxide-menu:client:inputSubmit', function(index, value, item)
    print(item.label .. ': ' .. value)
end)

Client Events

oxide-menu:client:closed

Fired when any menu is closed.

AddEventHandler('oxide-menu:client:closed', function()
    print('Menu closed')
end)

oxide-menu:client:checkboxChange

Fired when a checkbox is toggled.

AddEventHandler('oxide-menu:client:checkboxChange', function(index, checked, item)
    -- index: 1-based item index
    -- checked: boolean state
    -- item: full item data
end)

oxide-menu:client:sliderChange

Fired when a slider value changes.

AddEventHandler('oxide-menu:client:sliderChange', function(index, value, item)
    -- index: 1-based item index
    -- value: numeric value
    -- item: full item data
end)

oxide-menu:client:inputSubmit

Fired when Enter is pressed in an input field.

AddEventHandler('oxide-menu:client:inputSubmit', function(index, value, item)
    -- index: 1-based item index
    -- value: string value
    -- item: full item data
end)

qb-menu:client:menuClosed

This event still fires for scripts that listen to it.

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

Server-Triggered Updates

The server can push updates to open menus:

-- Server-side: Update entire menu
TriggerClientEvent('oxide-menu:client:update', source, {
    items = newItems
})

-- Server-side: Update single item
TriggerClientEvent('oxide-menu:client:updateItem', source, 2, {
    description = 'Price: $50',
    disabled = true
})

Integration Examples

Simple Shop Menu

local function OpenShop()
    exports['oxide-menu']:open({
        id = 'shop-menu',
        title = '24/7 Store',
        subtitle = 'Welcome!',
        searchable = true,
        items = {
            { label = 'FOOD', isHeader = true },
            { label = 'Water', description = '$5', icon = 'water', serverEvent = 'shop:buy', args = { item = 'water' } },
            { label = 'Bread', description = '$8', icon = 'bread', serverEvent = 'shop:buy', args = { item = 'bread' } },
            { type = 'divider' },
            { label = 'SUPPLIES', isHeader = true },
            { label = 'Bandage', description = '$25', icon = 'bandage', serverEvent = 'shop:buy', args = { item = 'bandage' } },
        }
    })
end

Settings Menu with Interactive Elements

local settings = {
    musicVolume = 50,
    sfxVolume = 80,
    showHud = true,
}

local function OpenSettings()
    exports['oxide-menu']:open({
        id = 'settings',
        title = 'Settings',
        items = {
            { type = 'slider', label = 'Music Volume', min = 0, max = 100, value = settings.musicVolume },
            { type = 'slider', label = 'SFX Volume', min = 0, max = 100, value = settings.sfxVolume },
            { type = 'divider' },
            { type = 'checkbox', label = 'Show HUD', checked = settings.showHud },
        }
    })
end

AddEventHandler('oxide-menu:client:sliderChange', function(index, value, item)
    if item.label == 'Music Volume' then
        settings.musicVolume = value
        -- Apply music volume
    elseif item.label == 'SFX Volume' then
        settings.sfxVolume = value
        -- Apply SFX volume
    end
end)

AddEventHandler('oxide-menu:client:checkboxChange', function(index, checked, item)
    if item.label == 'Show HUD' then
        settings.showHud = checked
        -- Toggle HUD
    end
end)

Job Menu with Submenus

-- Register submenus
exports['oxide-menu']:register('police-armory', {
    title = 'Armory',
    items = {
        { label = 'Pistol', icon = 'fas fa-gun', serverEvent = 'police:getWeapon', args = { weapon = 'pistol' } },
        { label = 'Taser', icon = 'fas fa-bolt', serverEvent = 'police:getWeapon', args = { weapon = 'taser' } },
        { label = 'Handcuffs', icon = 'fas fa-link', serverEvent = 'police:getItem', args = { item = 'handcuffs' } },
    }
})

exports['oxide-menu']:register('police-vehicles', {
    title = 'Vehicle Spawn',
    items = {
        { label = 'Police Cruiser', serverEvent = 'police:spawnVehicle', args = { model = 'police' } },
        { label = 'Police SUV', serverEvent = 'police:spawnVehicle', args = { model = 'police2' } },
    }
})

-- Main menu
local function OpenPoliceMenu()
    exports['oxide-menu']:open({
        id = 'police-main',
        title = 'Police Department',
        subtitle = 'Officer Menu',
        items = {
            { label = 'Toggle Duty', icon = 'fas fa-clock', serverEvent = 'police:toggleDuty' },
            { type = 'divider' },
            { label = 'Armory', icon = 'fas fa-shield-alt', submenu = 'police-armory' },
            { label = 'Vehicles', icon = 'fas fa-car', submenu = 'police-vehicles' },
        }
    })
end

Persistent Shop Menu

-- Menu stays open after each purchase
local function OpenShop()
    exports['oxide-menu']:open({
        id = 'quick-shop',
        title = '24/7 Store',
        subtitle = 'Quick Buy Mode',
        persist = true,  -- Menu-level: all items keep menu open
        items = {
            { label = 'DRINKS', isHeader = true },
            { label = 'Water', description = '$2', icon = 'water', serverEvent = 'shop:buy', args = { item = 'water', price = 2 } },
            { label = 'Soda', description = '$3', icon = 'soda', serverEvent = 'shop:buy', args = { item = 'soda', price = 3 } },
            { type = 'divider' },
            { label = 'FOOD', isHeader = true },
            { label = 'Sandwich', description = '$5', icon = 'sandwich', serverEvent = 'shop:buy', args = { item = 'sandwich', price = 5 } },
            { type = 'divider' },
            { label = 'Done Shopping', icon = 'fas fa-check', persist = false },  -- Override: closes menu
        }
    })
end

Live Updating Shop (onSelect Return)

-- Shop with live stock updates using onSelect return value
local stock = { water = 10, bread = 5 }

local function GetShopItems()
    return {
        { label = 'ITEMS', isHeader = true },
        {
            label = 'Water',
            description = '$2 | Stock: ' .. stock.water,
            disabled = stock.water <= 0,
            _item = 'water'
        },
        {
            label = 'Bread',
            description = '$3 | Stock: ' .. stock.bread,
            disabled = stock.bread <= 0,
            _item = 'bread'
        },
        { type = 'divider' },
        { label = 'Exit', persist = false },
    }
end

local function OpenShop()
    exports['oxide-menu']:open({
        id = 'live-shop',
        title = 'Live Stock Shop',
        persist = true,
        items = GetShopItems(),
        onSelect = function(item, index, isPersisting)
            if not isPersisting or not item._item then return end

            if stock[item._item] > 0 then
                stock[item._item] = stock[item._item] - 1
                -- Return updated items to refresh menu
                return GetShopItems()
            end
        end
    })
end

Live Updating with updateItem

-- Update individual items for better performance
local function OpenCounterMenu()
    local counts = { 0, 0, 0 }

    exports['oxide-menu']:open({
        id = 'counters',
        title = 'Click Counters',
        persist = true,
        items = {
            { label = 'Counter 1', description = 'Clicks: 0', _idx = 1 },
            { label = 'Counter 2', description = 'Clicks: 0', _idx = 2 },
            { label = 'Counter 3', description = 'Clicks: 0', _idx = 3 },
        },
        onSelect = function(item, index, isPersisting)
            if not isPersisting or not item._idx then return end

            counts[item._idx] = counts[item._idx] + 1
            -- Update just this item (more efficient than full refresh)
            exports['oxide-menu']:updateItem(index, {
                description = 'Clicks: ' .. counts[item._idx]
            })
        end
    })
end

Live Updating with onRefresh Callback

-- Cleanest approach: separate data logic from refresh logic
local inventory = { water = 5, bread = 3 }

local function buildItems()
    return {
        { label = 'Water x' .. inventory.water, disabled = inventory.water <= 0, _item = 'water' },
        { label = 'Bread x' .. inventory.bread, disabled = inventory.bread <= 0, _item = 'bread' },
        { label = 'Exit', persist = false },
    }
end

local function OpenInventory()
    exports['oxide-menu']:open({
        id = 'inventory',
        title = 'Use Items',
        persist = true,
        items = buildItems(),
        onSelect = function(item)
            -- Just handle the action, don't worry about refresh
            if item._item and inventory[item._item] > 0 then
                inventory[item._item] = inventory[item._item] - 1
            end
        end,
        onRefresh = function()
            -- Called automatically after onSelect when persist=true
            return buildItems()
        end
    })
end

Vehicle Menu with Mixed Persist

-- Some items stay open, others close
local function OpenVehicleMenu()
    exports['oxide-menu']:open({
        id = 'vehicle-controls',
        title = 'Vehicle Options',
        items = {
            { label = 'TOGGLES', isHeader = true },
            -- These keep the menu open for quick toggling
            { label = 'Engine', icon = 'fas fa-power-off', persist = true, event = 'vehicle:toggleEngine' },
            { label = 'Lights', icon = 'fas fa-lightbulb', persist = true, event = 'vehicle:toggleLights' },
            { label = 'Lock', icon = 'fas fa-lock', persist = true, event = 'vehicle:toggleLock' },
            { type = 'divider' },
            { label = 'ACTIONS', isHeader = true },
            -- These close the menu (default behavior)
            { label = 'Store Vehicle', icon = 'fas fa-warehouse', serverEvent = 'vehicle:store' },
            { label = 'Transfer Keys', icon = 'fas fa-key', serverEvent = 'vehicle:transfer' },
        }
    })
end