REC_Notify
One notification system for every script on your server — five types, six positions on screen, a sound per type, and a handler to bend any of it per notification.
Category
FREEInstallation
Download the dependencies
Download main resource
Copy and paste this on your server.cfg
ensure REC_Notify
Done
enjoy it !
Notification types
Five types ship with a color, an icon and a sound of their own, all three held in config.types. Restyling every success notification on the server is one edit there.

| Type | Color | Icon | Sound |
|---|---|---|---|
success | #4ade80 | check | success.wav |
error | #f87171 | xmark | error.wav |
info | #60a5fa | info | info.wav |
warning | #fbbf24 | warning | warning.wav |
neutral | #cbd5e1 | bell | neutral.wav |
A payload naming a type that does not exist falls back to info. color and icon on the payload override the type for that one notification, which is how a job or a gang gets its own accent without a new type.
Position
config.ui.position sets the default for the whole server and payload.position overrides it per notification. Each of the six spots keeps its own stack, so notifications sent to different positions never wait behind each other.

config.ui = {
---[[
--- Default position (each notification can override it with payload.position)
---]]
---@type REC_Notify.Shared.Enum.Position
position = "top-right",
---[[
--- Maximum number shown at once
---]]
---@type integer
maxVisible = 5,
---[[
--- Stack new notifications on top (false stacks them below)
---]]
---@type boolean
newestOnTop = true,
}
maxVisible counts per position. Anything over the limit waits in a queue and slides in as soon as an older notification leaves, in the order it was sent.
width, gap and offset take any CSS length, so 1.6vw scales the margin with the screen while 330px pins the card width. The rest of config.ui covers the progress bar, the icon and a fontScale multiplier.Icons
config.types[].icon and payload.icon take one of these names.
![]()
Anything else goes in as a raw SVG path behind a path: prefix, drawn on a 24 × 24 grid.
Citizen.CreateThread(function()
exports.REC_Notify:notify({
type = "info",
title = "REC_Notify",
description = "This icon came from a raw SVG path.",
icon = "path:M12 2L2 7l10 5 10-5z",
color = "#a78bfa",
})
end)
Sound
config.sound.mode decides who plays the sound.
| Mode | Plays | Volume | Extra files |
|---|---|---|---|
nui | An audio file from web/build/sounds/ | config.sound.volume, or per type | The .wav files ship with the resource |
frontend | A built-in GTA sound | Fixed by the game | None |
Adding your own sound means dropping the file in web/build/sounds/ and naming it in config.types[].sound.file, or in payload.sound for a single notification. No rebuild — the NUI fetches it by name at runtime.
config.sound.enabled = false silences everything and wins over payload.playSound. When the NUI cannot play a file it reports back, and the built-in GTA sound of that type plays instead, so a missing or renamed file is never silent.
Command
Test commands, registered only while config.debugMode is true. The release build ships with it off.
| Command | Permission | Description |
|---|---|---|
/recnotify [type] [position] | None | Shows one test notification |
/recnotifyall | None | Shows one of every type |
/recnotifyclear | None | Dismisses everything on screen |
Exports
notify
Shows a notification to the player running the script.
| Argument | Type | Description |
|---|---|---|
payload | Payload | The table below |
Returns string? — the id of the notification, nil when nothing was shown.
exports.REC_Notify:notify(payload)
Citizen.CreateThread(function()
exports.REC_Notify:notify({
type = "success",
title = "REC_Notify",
description = "The vehicle has been stored.",
duration = 4000,
position = "top-right",
})
end)
Payload is REC_Notify.Shared.Notify.Payload. Every field is optional:
| Field | Type | Description |
|---|---|---|
type | NotifyType? | success / error / info / warning / neutral |
title | string? | Drawn in the accent color |
description | string? | Body text |
duration | integer? | Milliseconds. Falls back to config.ui.defaultDuration |
position | Position? | Falls back to config.ui.position |
icon | string? | An icon name, or path: and a 24 × 24 SVG path |
color | string? | Accent color as hex |
playSound | boolean? | Falls back to config.sound.default |
sound | string? | An audio file in web/build/sounds/, replacing the sound of the type |
id | string? | Sending the same id again replaces what is on screen |
title nor description is dropped, and notify returns nil.hide
Dismisses one notification before its time is up.
| Argument | Type | Description |
|---|---|---|
id | string | The id notify returned |
Returns boolean.
exports.REC_Notify:hide(id)
clear
Dismisses everything on screen, queued notifications included.
Takes no arguments. Returns boolean.
exports.REC_Notify:clear()
The server side takes the same three, with the target in front. playerId is integer|integer[] — a single id, an array of them, or -1 for everyone connected.
notify
Shows a notification to a player.
| Argument | Type | Description |
|---|---|---|
playerId | integer|integer[] | Target player, or -1 for everyone |
payload | Payload | The same table as the client side |
Returns boolean — false when the payload or the target was unusable.
exports.REC_Notify:notify(playerId, payload)
RegisterCommand("payday", function(source)
exports.REC_Notify:notify(source, {
type = "success",
title = "REC_Notify",
description = "Payday has landed.",
icon = "money",
})
exports.REC_Notify:notify(-1, {
type = "info",
description = "Payday has been paid out to everyone.",
})
end, true)
hide
Dismisses one of a player's notifications.
| Argument | Type | Description |
|---|---|---|
playerId | integer|integer[] | Target player, or -1 for everyone |
id | string | The id the notification was sent with |
Returns boolean.
exports.REC_Notify:hide(playerId, id)
payload.id yourself when you intend to dismiss a notification later.clear
Dismisses everything on a player's screen.
| Argument | Type | Description |
|---|---|---|
playerId | integer|integer[] | Target player, or -1 for everyone |
Returns boolean.
exports.REC_Notify:clear(playerId)
Handler
handler/cl_handler.lua runs on every notification before it is drawn. Returning false drops it, and editing notify in place changes how it looks — a color per job, a longer duration for one type, a mute during a cutscene.
---@param notify REC_Notify.Shared.Notify
---@return boolean
function handler:onShow(notify)
if notify.type == "error" then
notify.duration = 8000
end
return true
end
handler/sv_handler.lua does the same on the server with onNotify(playerId, payload), called once per target, so returning false skips that one player and leaves the rest alone.
Stracture
---@class REC_Notify.Shared.Config
local config = {}
---[[
--- Debug Mode
---]]
---@type boolean
config.debugMode = true
---[[
--- Look and behaviour of the notifications
---]]
config.ui = {
---[[
--- Default position (each notification can override it with payload.position)
---]]
---@type REC_Notify.Shared.Enum.Position
position = "top-right",
---[[
--- Maximum number shown at once
--- Anything over the limit waits in a queue until an older notification is gone
---]]
---@type integer
maxVisible = 5,
---[[
--- Stack new notifications on top (false stacks them below)
---]]
---@type boolean
newestOnTop = true,
---[[
--- Card width and the distance from the screen edge (CSS units)
---]]
---@type string
width = "330px",
offset = {
---@type string
x = "1.6vw",
---@type string
y = "3vh",
},
---@type string
gap = "10px",
---[[
--- Display time used when duration is omitted
---]]
---@type integer
defaultDuration = 5000, -- ms
---[[
--- Enter and exit animation time
---]]
---@type integer
animationDuration = 260, -- ms
---[[
--- Show the remaining time progress bar
---]]
---@type boolean
showProgress = true,
---[[
--- Show the icon
---]]
---@type boolean
showIcon = true,
---[[
--- Font size multiplier (1.0 is the default)
--- Applied to a 14.5px title and a 14px body
---]]
---@type number
fontScale = 1.0,
}
---[[
--- Colour, icon and sound per notification type
--- icon takes an icon from shared/sh_enum.lua or a custom SVG path ("path:M12 2L...")
--- file: an audio file in web/build/sounds/ (in development put it in web/public/sounds/ and run pnpm build)
--- frontend: a built-in GTA sound (used when mode = "frontend")
--- volume: to change the volume for this type only (nil falls back to config.sound.volume)
--- set sound to nil to make this type silent
---]]
---@type table<REC_Notify.Shared.Enum.NotifyType, REC_Notify.Shared.Config.Type>
config.types = {
success = {
color = "#4ade80",
icon = "check",
sound = {
file = "success.wav",
frontend = { set = "HUD_FRONTEND_DEFAULT_SOUNDSET", name = "SELECT", },
volume = nil,
},
},
error = {
color = "#f87171",
icon = "xmark",
sound = {
file = "error.wav",
frontend = { set = "HUD_FRONTEND_DEFAULT_SOUNDSET", name = "CANCEL", },
volume = nil,
},
},
info = {
color = "#60a5fa",
icon = "info",
sound = {
file = "info.wav",
frontend = { set = "HUD_FRONTEND_DEFAULT_SOUNDSET", name = "NAV_UP_DOWN", },
volume = nil,
},
},
warning = {
color = "#fbbf24",
icon = "warning",
sound = {
file = "warning.wav",
frontend = { set = "HUD_FRONTEND_DEFAULT_SOUNDSET", name = "FOCUSIN", },
volume = nil,
},
},
neutral = {
color = "#cbd5e1",
icon = "bell",
sound = {
file = "neutral.wav",
frontend = { set = "HUD_FRONTEND_DEFAULT_SOUNDSET", name = "NAV_UP_DOWN", },
volume = nil,
},
},
}
---[[
--- Sound settings
--- enabled = false silences every notification (it wins over the caller's playSound)
---]]
config.sound = {
---@type boolean
enabled = true,
---[[
--- Default used when the caller omits playSound
---]]
---@type boolean
default = true,
---[[
--- How to play it
--- "nui": plays an audio file from web/build/sounds/ through NUI (the volume can be changed)
--- "frontend": plays a built-in GTA sound (no extra files needed)
---]]
---@type REC_Notify.Shared.Enum.SoundMode
mode = "nui",
---[[
--- Volume (0.0 to 1.0)
--- Only used when mode = "nui"
---]]
---@type number
volume = 0.4,
}
return config
---@class REC_Notify.Shared.Config.Type
---@field color string
---@field icon string
---@field sound REC_Notify.Shared.Config.Sound|nil
---@
---@class REC_Notify.Shared.Config.Sound
---@field file string|nil
---@field frontend REC_Notify.Shared.Config.Sound.Frontend|nil
---@field volume number|nil
---@
---@class REC_Notify.Shared.Config.Sound.Frontend
---@field set string
---@field name string