FiveM notifications and progress bars: ESX, QBCore and ox_lib

Send notifications and show progress bars on ESX, QBCore and ox_lib with ShowNotification, Functions.Notify, lib.notify and lib.progressBar, plus one bridge function.

Each framework has its own way to show a message or a progress bar, and a script written for one fails on another:

text
attempt to index a nil value (global 'lib')

This article lists the calls for ESX, QBCore and ox_lib, for notifications and for progress bars, then shows a single bridge function so your script works on whichever one the server runs.

Notifications on ESX

On the client:

lua
ESX.ShowNotification('You picked up a package')

On the server, from the player object:

lua
local xPlayer = ESX.GetPlayerFromId(source)
xPlayer.showNotification('Your order is ready')

ESX here is the object from ESX = exports['es_extended']:getSharedObject(). Recent ESX versions also accept a type and a duration after the text. If you rely on them, test on your version.

Notifications on QBCore

On the client:

lua
local QBCore = exports['qb-core']:GetCoreObject()

QBCore.Functions.Notify('You picked up a package', 'success', 5000)

The type is 'primary', 'success' or 'error', and the last value is the time in milliseconds. From the server, send the event to the player:

lua
TriggerClientEvent('QBCore:Notify', source, 'Your order is ready', 'success')

On QBox the same QBCore calls keep working through its compatibility layer, and ox_lib notifications are a good alternative.

Notifications with ox_lib

ox_lib has its own notification, and it works the same on every framework. On the client:

lua
lib.notify({
    title = 'Delivery',
    description = 'You picked up a package',
    type = 'success',
    duration = 5000,
})

The type is 'inform', 'success', 'warning' or 'error'. From the server, trigger the event on the player:

lua
TriggerClientEvent('ox_lib:notify', source, {
    title = 'Delivery',
    description = 'Your order is ready',
    type = 'success',
})

The lib global needs shared_script '@ox_lib/init.lua' in your fxmanifest.lua, as described in the ox_lib context menu guide. Without it you get the lib nil error above.

Progress bars

With ox_lib, a progress bar is a call that waits and returns whether it finished:

lua
if lib.progressBar({
    duration = 5000,
    label = 'Repairing the engine',
    useWhileDead = false,
    canCancel = true,
    disable = {
        move = true,
        car = true,
        combat = true,
    },
    anim = {
        dict = 'mini@repair',
        clip = 'fixing_a_ped',
    },
}) then
    print('finished')
else
    print('cancelled')
end

lib.progressCircle takes the same options and shows a circle instead of a bar. Both can also attach a prop to the player.

QBCore's version uses a callback instead of waiting:

lua
QBCore.Functions.Progressbar('repair_engine', 'Repairing the engine', 5000, false, true, {
    disableMovement = true,
    disableCarMovement = true,
    disableMouse = false,
    disableCombat = true,
}, {
    animDict = 'mini@repair',
    anim = 'fixing_a_ped',
    flags = 49,
}, {}, {}, function()
    print('finished')
end, function()
    print('cancelled')
end)

The arguments are, in order: a name, the label, the duration, useWhileDead, canCancel, the disabled controls, the animation, two props, then the finish and cancel callbacks. ESX has no progress bar of its own that every server shares, so on ESX use lib.progressBar.

One bridge function for all of them

To keep your scripts framework-free, write the framework detail once and call your own functions everywhere. Put this in a client file:

lua
local function started(name)
    return GetResourceState(name) == 'started'
end

function Notify(message, notifyType)
    notifyType = notifyType or 'inform'

    if started('ox_lib') then
        lib.notify({ description = message, type = notifyType })
    elseif started('qb-core') then
        local qbType = notifyType == 'inform' and 'primary' or notifyType
        exports['qb-core']:GetCoreObject().Functions.Notify(message, qbType)
    elseif started('es_extended') then
        exports['es_extended']:getSharedObject().ShowNotification(message)
    else
        print(message)
    end
end

-- returns true when finished, false when cancelled
function Progress(label, duration)
    if started('ox_lib') then
        return lib.progressBar({
            duration = duration,
            label = label,
            canCancel = true,
            disable = { move = true, car = true },
        })
    end

    local p = promise.new()
    exports['qb-core']:GetCoreObject().Functions.Progressbar('bridge_progress', label, duration, false, true, {
        disableMovement = true,
        disableCarMovement = true,
    }, {}, {}, {}, function() p:resolve(true) end, function() p:resolve(false) end)
    return Citizen.Await(p)
end

Now your script only calls Notify('Done', 'success') and Progress('Working', 5000). If ox_lib is installed, it is used first. Otherwise QBCore is used, and notifications fall back to ESX.

The fallback progress bar only covers QBCore. On a server with neither ox_lib nor QBCore, add your own branch or make ox_lib a required dependency with dependency 'ox_lib'.

Tip: do the same on the server with one NotifyPlayer(source, message, type) function, using the server calls from the sections above. Then no other file needs to know which framework runs.

Checklist

Symptom Fix
attempt to index a nil value (global 'lib') Add shared_script '@ox_lib/init.lua' and start ox_lib first
Server notification does nothing Trigger the client event with the player's source as the second argument
QBCore notification has no colour Use a valid type: 'primary', 'success' or 'error'
Progress bar blocks the rest of the function lib.progressBar waits: run it in a thread or handler
Player can walk during the bar Set disable (ox_lib) or disableMovement (QBCore)
Script must work on ESX and QBCore Use the bridge functions and call only those

Quick answers

How do I send a notification from the server?

On ESX use xPlayer.showNotification(msg). On QBCore use TriggerClientEvent('QBCore:Notify', source, msg, 'success'). With ox_lib use TriggerClientEvent('ox_lib:notify', source, { description = msg, type = 'success' }).

Does lib.progressBar block the script?

Yes. It waits until the bar finishes and returns true, or false if the player cancelled. Run it in a thread or an event handler, not in a place that must not wait.

Why is lib nil when I call lib.notify?

Your manifest is missing shared_script '@ox_lib/init.lua', or ox_lib is not started before your resource.

Scripts that skip this problem

Quest CreatorA visual editor for quests and NPC dialogues, built node by node in game.View script →Advanced BoostingTablet-driven vehicle boosting: contracts from class D to S+, crews and a live queue.View script →Mic PhoneA foldable phone that unfolds into a tablet and carries onto a player's real phone.View script →

Keep reading