Skip to content

Migration guide

Migrate from ox_lib, RageUI / RageNativeUI, qb-menu or esx_menu_default to LastMenu — equivalence tables and side-by-side examples.

3 min read

Prerequisites

First: declare LastMenu in client resources that use it.

-- In each client resource's fxmanifest.lua:
client_scripts { '@LastMenu/client/exports.lua' }
-- In server.cfg, BEFORE dependent resources:
ensure LastMenu

Short reference in code:

local UI = exports['LastMenu']

Key conceptual differences

No render loop

ox_lib, RageUI and NativeUI require an active loop. LastMenu uses a reactive polling engine — declare what’s dynamic, the lib does the rest.

-- LastMenu: dynamic label without loop
menu:button(function()
    return "Money: " .. GetPlayerMoney() .. " €"
end, { refresh = 500 })

Handles instead of string IDs

-- ox_lib
lib.registerContext({ id = 'shop', ... })
lib.showContext('shop')

-- LastMenu
local shopMenu = UI:context_build(function(menu) ... end)
shopMenu:open()
shopMenu:close()

Automatic navigation stack

In RageUI / ox_lib, you manually manage which menu is open. In LastMenu, each UI:context(...) in a callback pushes to the stack — Escape automatically pops.


Migration from ox_lib

Context menu

ox_libLastMenu
lib.registerContext({ id, title, options }) + lib.showContext('id')UI:context(fn)
{ title, description, icon, onSelect }menu:button(label, { icon, hint, cb })
metadata = { { label, value } }preview = { stats = { { label, value, max } } }
disabled = truedisabled = true
arrow = truearrow = true
-- ox_lib
lib.registerContext({
    id      = 'garage',
    title   = 'Garage',
    options = {
        {
            title       = 'Repair engine',
            description = 'Requires 500 €',
            icon        = 'wrench',
            onSelect    = function() repairEngine() end,
        },
    },
})
lib.showContext('garage')

-- LastMenu
UI:context(function(menu)
    menu:title("Garage")
    menu:button("Repair engine", {
        icon = "wrench",
        hint = "500 €",
        cb   = function() repairEngine() end,
    })
end)

Input dialog

ox_libLastMenu
lib.inputDialog(title, rows)UI:input_async(fn)
{ type = 'input', label }b:field(label, { type = 'text' })
{ type = 'number', min, max }b:field(label, { type = 'number', min, max })
Returns nil if cancelledReturns nil if cancelled

Alert dialog

ox_libLastMenu
lib.alertDialog({ header, content })UI:alert_async(fn)
Returns 'confirm' or 'cancel'Returns true or false

Notifications

ox_libLastMenu
lib.notify({ description, type, duration })UI:notify(fn)
type = 'inform'n:type("info")
-- ox_lib
lib.notify({ description = 'Engine repaired.', type = 'success', duration = 3000 })

-- LastMenu
UI:notify(function(n)
    n:message("Engine repaired.")
    n:type("success")
    n:duration(3000)
end)

Target (ox_target)

ox_targetLastMenu
exports.ox_target:addLocalEntity(entity, opts)UI:target_add_entity(entity, opts)
exports.ox_target:addModel(models, opts)UI:target_add_model(model, opts)
exports.ox_target:addSphereZone(opts)UI:target_add_sphere(coords, radius, opts)
exports.ox_target:addBoxZone(opts)UI:target_add_box(coords, opts)
exports.ox_target:addPolyZone(opts)UI:target_add_poly(points, opts)
{ onSelect = fn }{ cb = fn }
canInteract = function(entity, ...) endcondition = function(entity) return bool end

Progress bar

ox_libLastMenu
lib.progressBar({ duration, label })UI:progress(fn)
Blocking (coroutine)Non-blocking (callbacks)
anim = { dict, clip, flag }b:anim({ dict, clip, flag })

Migration from RageUI / RageNativeUI

Main menu

RageUILastMenu
RageUI.Menu("title", "subtitle")UI:context(fn)
RageUI.Item("label", "desc")menu:button(label, { hint = desc })
RageUI.CheckboxItem("label", state)menu:checkbox(label, { default = state })
RageUI.ListItem("label", list, index)menu:list(label, { items = list, default = index })
RageUI.SliderItem("label", min, max, val)menu:slider(label, { min, max, default = val })
RageUI.Render(fn) — per-frame loopNo loop needed
-- RageUI (loop + render)
local menuVisible = false
local menu = RageUI.Menu("Garage", "")

Citizen.CreateThread(function()
    while true do
        Citizen.Wait(0)
        if menuVisible then
            RageUI.Render(function()
                RageUI.UseMenu(menu, function()
                    local item = RageUI.Item("Réparer", "500 €")
                    if item.Activated then repairEngine() end
                end)
            end)
        end
    end
end)

-- LastMenu (zero loops)
RegisterCommand('garage', function()
    UI:context(function(menu)
        menu:title("Garage")
        menu:button("Réparer", { hint = "500 €", cb = function() repairEngine() end })
    end)
end, false)

RageUI submenus

-- RageUI
local subMenu = RageUI.Menu("Submenu", "")
local _, activated = RageUI.SubMenu(subMenu, "Go to submenu")
if activated then RageUI.OpenMenu(subMenu) end

-- LastMenu
menu:submenu("Go to submenu", function(sub)
    sub:title("Submenu")
    sub:button("Action", { cb = function() end })
    sub:back()
end)

Migration from qb-menu / esx_menu_default

qb-menu

qb-menuLastMenu
exports['qb-menu']:openMenu(items)UI:context(fn)
{ header, txt, isMenuHeader }menu:header(label)
{ header, txt, params = { event, args } }menu:button(label, { cb = function() TriggerEvent(...) end })
{ header, txt, disabled }menu:button(label, { disabled = true })
-- qb-menu
exports['qb-menu']:openMenu({
    { header = "Garage", isMenuHeader = true },
    {
        header = "Repair engine",
        txt    = "Cost: 500 €",
        params = { event = "garage:repairEngine", args = {} },
    },
})

-- LastMenu
UI:context(function(menu)
    menu:header("Garage")
    menu:button("Repair engine", {
        hint = "500 €",
        cb   = function() TriggerEvent("garage:repairEngine") end,
    })
end)

Complete equivalence table

Featureox_libRageUIqb-menuLastMenu
Context menuregisterContext + showContextRageUI.Menu + loopopenMenu(items)UI:context(fn)
Reusable contextre-showmenu poolUI:context_build(fn)
Input (callback)UI:input(fn)
Input (blocking)lib.inputDialogUI:input_async(fn)
Alert (callback)UI:alert(fn)
Alert (blocking)lib.alertDialogUI:alert_async(fn)
Notificationlib.notifyUI:notify(fn)
Radial menulib.radialMenuUI:radial(fn)
Progress barlib.progressBarUI:progress(fn)
Target entityox_target:addLocalEntityUI:target_add_entity
Target sphereox_target:addSphereZoneUI:target_add_sphere
Real-time reactivity
Zero dependencies
Automatic stack⚠️ partial✅ manual✅ automatic
Controller⚠️
CSS theming⚠️