PGNS | A Global UI Notification API

pgns image

Performant, ergonomic, and customizable centered prompt notification API



PGNS is the sequel of my previous notification systems that I’ve showed off years ago. I never publicized those, but people are still showing interests in them, so I decided to make a new version that will be open source. Likewise, I posted this just for the sake of making it public; I don’t intend on actively maintaining this outside of using it for my own projects.


Features

  • Organize messages by priority
  • Realtime updates to existing messages
  • Built-in emoji translator (using the format :emoji_name:)
  • Custom image decals element
  • Custom loading bar element
  • Customizable and expandable message presets
  • Fancy message animations

API

There are numerous exposed functions in the module that gives you a lot of control over how the notifications are handled. I will only go through all of the essential interface functions, you can check out the API code to see everything on your own time!

--client API
pgns.Do(constructor)
pgns.new(constructor)

--server API
pgns.BroadcastPlayer(player, constructor)
pgns.EphemeralPlayer(player, constructor)
pgns.BroadcastAll(constructor)
pgns.EphemeralAll(constructor)
pgna.BroadcastGlobal(constructor)

Do(constructor): string client

Create/update a notification. If an ID is provided, it will update the existing message with the ID, or create a new message with the ID if it doesn’t exist. In other words, this function guarantees the existence of a message with a specific ID.
Updating a message will only change the message’s text content and its duration, nothing else.
If no ID is provided, it is identical to the new() function.

Returns the ID of the message.

new(constructor): string client

Create a new notification. If the specified ID is already in use, it will generate a new ID. Unlike the Do() function, this function will guarantee creating a new message.

Returns the ID of the message.

BroadcastPlayer(player, constructor) server

Sends a message to the targeted player.

EphemeralPlayer(player, constructor) server

Sends a message to the targeted player using UnreliableRemoteEvents. This should be used instead of BroadcastPlayer() if you are frequently updating a message.

BroadcastAll(constructor) server

Sends a message to all players in the server.

EphemeralAll(constructor) server

Sends a message to all players in the server but using UnreliableRemoteEvents.

BroadcastGlobal(constructor) server

Sends a message to all players in your game via MessagingService. This is a dangerous thing to do :warning:

The API looks simple until you realize that I haven’t told you what constructor is. It is the universal data structure that PGNS uses to create/update notification messages. It’s a mixed table with the following definition:

export type Constructor = {
	[number]: number | string,
	
	ID: string?, id: string?,
	Type: string?, t: string?,
	NoClick: boolean?, nc: boolean?,
	Duration: number?, d: number?,
	Priority: number?, p: number?,
	Silent: boolean?, s: boolean?,
}

Why did I do this? It makes it many times easier to use the API when you can optionally declare or ignore specific parameters. Otherwise, if it was a long function parameter list, you would often have to substitute values you don’t want to change with nil. Also, it’s harder to remember which parameter corresponds with which config value, especially if there are issues with the intellisense linting. A mixed table solves all of these issues, since you can directly specify which field you are changing and any optional fields can just be ignored. Lua’s syntactical sugar of omitting parentheses when using a single table parameter perfectly suits this use case!

--Look how messy this is!
pgns.Do({'this is a text', -50}, 'testMsg', 'Warning', false, nil, nil, false)

--This is so much cleaner and legible!
pgns.Do{'this is a text', -50, id='testMsg', t='Warning', s=false}

Rant aside, let’s look into each one of these fields:

(numerical index) constructor[1], constructor[2], constructor[3]...

The actual message itself is stored within the array portion of the constructor table. They are ordered by their numerical index.

string values denote texts. RichText is supported.
positive integers denote custom image decals. Use the the number that you find in the image’s rbxassetid://... link.
negative integers in the range [-100, -0] represents loading bar elements. -0 is 0% progress, and -100 is 100% progress. It’s important to use negative signs, because that’s how the module tells this apart from an decal element!

Example: {'hello world', ':wave:', 8394622189, -50}

ID constructor.ID or constructor.id

Allows you to tag messages with an unique ID so you can track them and update them later.

Example: {'hello world', id='greetPlayer'}

Type constructor.Type or constructor.t

Which preset to use to show the message. Defaults to Default.

Example: {'something went wrong!', t='Error'}

NoClick constructor.NoClick or constructor.nc

If enabled, the player cannot click on the message to hide them; it must expire naturally by depleting its duration, or manually via the API’s pgns.End(ID) function. Defaults to nil.

Example: {"haha I'm stuck on your screen", nc=true}

Duration constructor.Duration or constructor.d

Number of seconds the message lasts for before it disappears. Set to -1 to make the message last indefinitely. Different presets have different default durations.

Example: {"hi", d=10}

Priority constructor.Priority or constructor.p

A number that determines how far up the message should display/displace. Higher priorities are displayed first in the notification stack. Preset messages have different default priorities.

Example: {"Did you pray today", p=math.huge}

Silent constructor.Silent or constructor.s

Only used for updating messages. This field is ignored when creating a new message. If set to true, the message will update without playing any animations. You should keep this on for messages that will update very often, such as countdowns. Defaults to TRUE!!!

Example: {'Progress:', -50, s=true}

It looks complicated! But I promise you, when you’re actually using it, it’s incredibly simple. Here are a few examples:

--greet the localplayer when they join
local res = game:GetService('ReplicatedStorage')
local pgns = require(res:WaitForChild('PGNS'):WaitForChild('ClientMessageAPI'))

pgns.Do{':wave:', 'welcome to my game!'} --emoji translation only works if they are in their own spot
--sending a message to everyone in the server
local res = game:GetService('ReplicatedStorage')
local pgns = require(res:WaitForChild('PGNS'):WaitForChild('ServerMessageAPI'))

local function intermissionStarted()
  pgns.BroadcastAll{'Intermission ends in 15 seconds.', t='Warning', id='intermission'}
  task.delay(3, pgns.BroadcastAll, {'Battle stations at main gate.', id='intermission',d=5, s=false})
end
--show the player a loading bar that takes 10 seconds to complete
local res = game:GetService('ReplicatedStorage')
local pgns = require(res:WaitForChild('PGNS'):WaitForChild('ClientMessageAPI'))

for i = 1, 100 do
  pgns.Do{id='loadingBar123', 'Loading...', -i} --notice how "i" is being negated!
  task.wait(0.1)
end
pgns.Do{id='loadingBar123', 'Loaded!', d=2} --update new duration to be 2 seconds

There are plenty more examples inside the demo place under workpace.for testing!

Download

The most up-to-date version of PGNS can be found in the uncopylocked demo place here:

And additionally as a model on the Creator store:

A direct rbxm download of the module is available here:
:warning: The rbxm file is not guaranteed to be the latest version of this module.

PGNS 6.6.2025.rbxm (55.5 KB)

If you want to see the source code immediately, a version of it is uploaded onto Github:
:warning: The Github source code is not guaranteed to be the latest version of this module.

Installation

Installation is extremely simple: The entire PGNS module is packed inside a single folder located in ReplicatedStorage, and is used as-is. Everything runs directly inside the folder; you don’t need to “unpack” the module, you can just drop it in your game and start using it!

Before Using…

There are some changes you may want to make before you add PGNS to your game. There is a chat command for use by operators to broadcast a message to all players in the server. Permission is checked via group ranks, so you should edit this module under PGNS/MessageFunctions:
image
The chat command uses the JSON format to interpret the constructor datatype.
image

Additionally, for convenience, PGNS/ServerMessageAPI automatically adds _G integration with various commands that allows developers with edit permissions to use the console to utilize PGNS. You should edit or remove this section depending on your use case.
image
ScreenShot_20250606175323

These are not the only things you should look out for; I highly encourage you to go through the entire module to look for things you might want to change; after all, this is going into YOUR game!

20 Likes

cool stuff man, dont know anything about this stuff but it looks cool

looks very clean and well made,
could you upload it as a model though? it will make downloading it a 100 times easier.

ill definitely be copping this though, thanks.

EDIT: i like the priority example :moyai:

Affirmative.

I didn’t want to do it at first because it’s quite a hassle to update, it’s easier for me to just push changes to the live demo :stuck_out_tongue:

thank you, you should upload the model to roblox though so you dont have to update this topic each time with the newest rbxm, that’ll make things easier for you and for us.

all you’ll have to do for updates is replace the uploaded model in studio.

aye aye captain!

crazy work, how long did this take you? very nice notification system
10/10

I slowly worked on it over the course of a year or two whenever I had the time.

1 Like