Resources

REC_Notify

Welcome to REC_Notify documentation.

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

FREE

Installation

Download the dependencies

Docs - REC_Library

Follow the installation

Download main resource

GitHub - REC_Notify

Download from here!!

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.

TypeColorIconSound
success#4ade80checksuccess.wav
error#f87171xmarkerror.wav
info#60a5fainfoinfo.wav
warning#fbbf24warningwarning.wav
neutral#cbd5e1bellneutral.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.

sample.lua
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.

ModePlaysVolumeExtra files
nuiAn audio file from web/build/sounds/config.sound.volume, or per typeThe .wav files ship with the resource
frontendA built-in GTA soundFixed by the gameNone

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.

CommandPermissionDescription
/recnotify [type] [position]NoneShows one test notification
/recnotifyallNoneShows one of every type
/recnotifyclearNoneDismisses everything on screen

Exports

Client side exports

notify

Shows a notification to the player running the script.

ArgumentTypeDescription
payloadPayloadThe table below

Returns string? — the id of the notification, nil when nothing was shown.

exports.REC_Notify:notify(payload)
sample.lua
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:

FieldTypeDescription
typeNotifyType?success / error / info / warning / neutral
titlestring?Drawn in the accent color
descriptionstring?Body text
durationinteger?Milliseconds. Falls back to config.ui.defaultDuration
positionPosition?Falls back to config.ui.position
iconstring?An icon name, or path: and a 24 × 24 SVG path
colorstring?Accent color as hex
playSoundboolean?Falls back to config.sound.default
soundstring?An audio file in web/build/sounds/, replacing the sound of the type
idstring?Sending the same id again replaces what is on screen
A payload carrying neither title nor description is dropped, and notify returns nil.

hide

Dismisses one notification before its time is up.

ArgumentTypeDescription
idstringThe 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()
Server side exports

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.

ArgumentTypeDescription
playerIdinteger|integer[]Target player, or -1 for everyone
payloadPayloadThe same table as the client side

Returns booleanfalse when the payload or the target was unusable.

exports.REC_Notify:notify(playerId, payload)
sample.lua
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.

ArgumentTypeDescription
playerIdinteger|integer[]Target player, or -1 for everyone
idstringThe id the notification was sent with

Returns boolean.

exports.REC_Notify:hide(playerId, id)
The id is issued on the client, so the server never learns it on its own. Send payload.id yourself when you intend to dismiss a notification later.

clear

Dismisses everything on a player's screen.

ArgumentTypeDescription
playerIdinteger|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

config/sh_config.lua
  ---@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
©2026 RE:CORD Development Group. All rights reserved.