# Building a custom notification UI for FiveM

> Create your own FiveM notification system: an export and event API, a NUI stack with types, durations and a queue, placement that avoids the minimap and chat, sounds, and routing ESX, QBCore and ox_lib notifications through one consistent design.

Source: https://fam-tools.com/guides/fivem-notification-ui · FAM Tools · published 2026-09-11 · updated 2026-09-11 · about 10 min

**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.

## Key points

- One API: a client export and a server-triggerable event.
- Types (success, error, info, warning) with icons and colours.
- Cap the stack (3–5) and merge duplicates.
- Top-right or top-centre; keep clear of minimap and chat.
- Forward framework notify functions to your export.

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*

```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*

```lua
-- 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*

```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*

```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](https://fam-tools.com/guides/fivem-hud-design-principles).

## Routing framework notifications

| Source | How to redirect |
| --- | --- |
| ESX | Edit `ESX.ShowNotification` in es_extended’s client functions to call your export |
| QBCore | Edit `QBCore.Functions.Notify` in qb-core’s client functions (the `QBCore:Notify` event uses it) |
| ox_lib | Leave `lib.notify` as is and match its style, or wrap it in your own scripts |
| Your scripts | Call `exports.mynotify:notify` directly |

> **Framework updates** — Edits inside framework files are lost when you update the framework. Keep a note of the change so you can re-apply it.

## 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.

## Related

- https://fam-tools.com/guides/fivem-hud-design-principles
- https://fam-tools.com/guides/fivem-nui-basics
- https://fam-tools.com/guides/fivem-nui-performance
- https://fam-tools.com/guides/fivem-nui-callbacks-fetch
