Building a custom notification UI for FiveM

10 min read4 sections · 4 questions answered
Short answer

Build a small resource with a client export (exports.mynotify:notify({ title, text, type, duration })) and a matching net event for the server, which sends a NUI message to a stack of toasts. Limit how many show at once, deduplicate repeats, place the stack away from the minimap and chat, and route framework notifications (ESX, QBCore, ox_lib) through it so everything looks the same.

Notifications are how every script talks to players — and on most servers they come in three different styles because ESX, QBCore, ox_lib and each job script ship their own. A single notification resource with a clean API gives the whole server one voice.

The Lua API

client.lua
local function notify(data)
    if type(data) == 'string' then data = { text = data } end
    SendNUIMessage({
        action = 'notify',
        title = data.title,
        text = data.text or '',
        type = data.type or 'info',
        duration = data.duration or 5000,
    })
end

exports('notify', notify)
RegisterNetEvent('mynotify:notify', notify)
Usage
-- client
exports.mynotify:notify({ title = 'Garage', text = 'Vehicle stored', type = 'success' })
-- server
TriggerClientEvent('mynotify:notify', src, { text = 'Payment received', type = 'success' })

The notification stack

app.js
const stack = document.querySelector('#notify');
const MAX = 4;

window.addEventListener('message', ({ data }) => {
  if (data.action !== 'notify') return;

  const same = [...stack.children].find((n) => n.dataset.key === data.type + data.text);
  if (same) {
    const count = Number(same.dataset.count || 1) + 1;
    same.dataset.count = count;
    same.querySelector('.count').textContent = '×' + count;
    return;
  }

  const el = document.createElement('div');
  el.className = 'toast ' + data.type;
  el.dataset.key = data.type + data.text;
  el.innerHTML = '<strong></strong><p></p><span class="count"></span>';
  el.querySelector('strong').textContent = data.title || '';
  el.querySelector('p').textContent = data.text;
  stack.prepend(el);

  while (stack.children.length > MAX) stack.lastElementChild.remove();
  setTimeout(() => el.remove(), data.duration);
});

Setting textContent instead of innerHTML for the message stops scripts (or players, through chat-driven messages) from injecting HTML into your UI.

style.css
#notify { position: fixed; top: 3vh; right: 2vw; display: flex; flex-direction: column; gap: 0.8vh; width: 22vw; }
.toast { background: rgba(14, 14, 18, 0.9); border-left: 3px solid #4f8cff; border-radius: 8px; padding: 1vh 1vw; color: #eee; font-size: 1.5vh; }
.toast.success { border-color: #3ecf8e; }
.toast.error { border-color: #ff5d5d; }
.toast.warning { border-color: #ffb020; }

Placement and behaviour

  • Top-right or top-centre: the minimap is bottom-left and chat is usually top-left.
  • 4–6 seconds for normal messages; longer for errors players must read.
  • A short, quiet sound for errors only — constant sounds get muted.
  • Hide notifications in the pause menu, like the rest of the HUD — see HUD design.

Routing framework notifications

SourceHow to redirect
ESXEdit ESX.ShowNotification in es_extended’s client functions to call your export
QBCoreEdit QBCore.Functions.Notify in qb-core’s client functions (the QBCore:Notify event uses it)
ox_libLeave lib.notify as is and match its style, or wrap it in your own scripts
Your scriptsCall exports.mynotify:notify directly

Common questions

How do I make custom notifications in FiveM?

Create a resource with a client export and event that send NUI messages to a stack of toast elements in your UI.

How do I replace ESX notifications?

Change ESX.ShowNotification in es_extended’s client functions to call your notification export.

How do I replace QBCore notifications?

Change QBCore.Functions.Notify in qb-core’s client functions to call your export.

How many notifications should show at once?

Around three to five; merge duplicates so spam does not fill the screen.

Want a loading screen you never have to debug?

Build it in the browser, export once, and edit it whenever — no HTML, no re-uploads.

Start free

More guides