RBLX Essentials Documentation
Overview
RBLX Essentials is a collection of modular, production-ready Roblox game systems distributed as plug-and-play packs. Each pack installs in minutes via a dedicated Studio plugin, requires no manual scripting to set up, and integrates automatically with the other packs in the ecosystem.
The goal is to give Roblox developers a solid, tested foundation so they can focus on what makes their game unique instead of rebuilding common systems from scratch.
Pack Catalogue
| Pack | PackId | Version | Price | Description |
|---|---|---|---|---|
| Easy Pet System | EasyPetSystem |
2.1.0 | $14.99 | Complete pet collection system with eggs, rarity tiers, hatching animations and pet following. |
| Sound System & Music Player | SoundSystem |
2.1.0 | Free | Centralized SFX and music infrastructure shared across all packs. |
| General UI Kit | GeneralUIKit |
2.1.0 | $14.99 | Tag-based UI framework with panels, buttons, tooltips and animations. |
| Datastore & Purchase System | DatastorePurchaseSystem |
2.1.0 | Free | Persistent player data and Robux purchase handling shared across all packs. |
| Easy Leaderstats & Leaderboards System | EasyLeaderstatsSystem |
2.0.0 | $7.99 | Tab-menu leaderstats and physical in-world global leaderboards, both plugin-managed. |
| Roulette System | RouletteSystem |
2.1.0 | $7.99 | Weighted-chance reward wheel with free spins, cooldowns and custom reward modules. |
| Time Rewards System | TimeRewardsSystem |
2.1.0 | $4.99 | A 7-day daily reward cycle plus timed in-session gifts, with custom reward modules. |
| Codes System | CodesSystem |
2.1.0 | $4.99 | Redeemable codes for in-game rewards, authored in Settings with expiry dates. |
| Notifications System | NotificationsSystem |
2.1.0 | Free | Toasts, chat lines and banners, with triggers any pack can fire through the kernel. |
Dependency Matrix
The shared UI/runtime foundation ships embedded inside every pack — nothing to install separately. The table below shows the remaining dependencies and synergies between packs.
| Sound System | Datastore | UI Kit | Pet System | Leaderstats | |
|---|---|---|---|---|---|
| Sound System Free |
— | Pairs | Pairs | ||
| Datastore & Purchase Free |
— | Pairs | Pairs | Pairs | |
| General UI Kit $14.99 |
Pairs | Pairs | — | Pairs | |
| Easy Pet System $14.99 |
Pairs | Bundled | Pairs | — | |
| Easy Leaderstats $7.99 |
Bundled | — |
Architecture Philosophy
Modular. Each pack is a self-contained Rojo project. You install only what you need.
Plugin-managed. A pack arrives as one self-contained container you drag into your place. The plugin installs it into the services it belongs in, with anything it depends on installed first, in one click. Nothing is placed by hand and there is no install order to remember.
Configuration over code. Every setting lives in that pack's own Settings folder, and the plugin renders it as a panel built for that pack rather than a generic list of values. You change things in the plugin; you do not touch scripts.
Shared infrastructure. One kernel boots every pack in dependency order and hands each one the others' APIs. A pack reaches another only by declaring it, so there is no hidden coupling, and optional companions are resolved at runtime instead of being made mandatory.
Getting Started
Requirements: Roblox Studio (any recent version). API Services must be enabled in your place if you use Datastore & Purchase System or Easy Leaderstats & Leaderboards System.
Step 1: Install the Plugin
Download the RBLX Essentials Plugin (free) from Itch.io and drag the .rbxm file into Studio. It installs automatically to your Plugins folder. Restart Studio once.
The plugin appears as RBLX Essentials in the Plugins toolbar.
Step 2: Drag a Pack into Studio
- Buy and download any pack's
.rbxmfrom Itch.io. - Drag the file into Roblox Studio.
- Open the plugin (Plugins → RBLX Essentials) — it shows a banner for the container it just detected.
Each pack now ships fully self-contained — there is no separate Ecosystem Core download and no required install order between packs. Drag in whichever packs you own, in any order.
Step 3: Install the Pack
Click Install on the banner. That's it — the pack deploys itself to the correct services.
A couple of packs expect another pack's data to exist:
- Easy Pet System and Easy Leaderstats & Leaderboards System read and write through Datastore & Purchase System — install that pack first if you're using either of them.
- General UI Kit and Sound System have no install-order requirement; Sound System pairs with the other packs for audio but is optional everywhere.
Step 4: Enable API Services
If you are using Datastore & Purchase System or Easy Leaderstats & Leaderboards System, go to:
Game Settings → Security → Enable Studio Access to API Services
Without this, player data and global leaderboards will not save in Studio playtests.
Step 5: Configure
Select any installed pack in the plugin to open its settings panel. Change values directly in the panel, no script editing required.
Typical things to configure:
- Easy Pet System: egg prices, drop rates, pet multipliers, gamepass IDs.
- Datastore & Purchase System: auto-save interval, DataStore name.
- Sound System: which Music Player template to use, enable/disable Music Player.
Step 6: Press Play
Hit Play in Studio. All packs initialise automatically. No bootstrap code, no wiring required.
Updating a Pack
Download the new version's .rbxm and drag it into Studio. The plugin shows an update banner for that pack — click Update. Your code is overwritten with the new version; your Settings are preserved.
Migrating from v1
Installing the v2 plugin replaces your old RBLX Essentials plugin file. Open it and every v1 pack it detects shows a Migrate banner: click Migrate to uninstall the v1 copy and capture its settings, then install the matching v2 .rbxm and click Apply mapped settings to carry your old configuration over automatically. If you had Ecosystem Core installed for v1, migrate it the same way — its runtime now ships embedded in each pack, so there's no v2 download for it.
Uninstalling a Pack
Select the pack in the plugin and click Uninstall Pack. The pack's deployed code is removed from all services.
Plugin
The plugin is the manager for the whole ecosystem. It finds the packs you have, installs them into the right places, resolves what they depend on, and gives each one a settings panel built for that pack. It is free, and it is how every pack is meant to be installed.
Every pack in one list
Drop a pack file into your place and it appears here. One click installs it into the services it belongs in, with anything it depends on installed first and in the right order. The same list tells you which packs are behind the current version.
Pack Banners
| Banner | Meaning |
|---|---|
Install | A pack container was dragged into Studio and is ready to deploy. |
Update | A newer version of an installed pack was dragged in. The code is replaced; your Settings are carried over. |
Migrate | A v1 install of this pack was detected. Migrating uninstalls the v1 copy and captures its settings for the v2 install. |
Apply mapped settings | Shown after installing a v2 pack following a migration. Applies the captured v1 settings onto it. |
Settings built for each pack
This is the part that is not a generic form. Eight packs have a panel written for them, and what you edit is what you see.
Roulette's wheel is the real one, redrawn as you change a slot. Time Rewards previews the streak rail. Notifications renders each notification card at its true size and puts a pencil beside it that makes it appear on screen and selects it in the Explorer. Datastore prints the finished save a new player will receive, as JSON you can select and copy.
Easy Pet System spins each pet in 3D while you set its rarity and multiplier, and tells you when an egg's drop table does not add up to 100%. General UI Kit lists your menus and HUD buttons. Sound System measures every sound's real loudness in decibels and can pull the outliers into line in one press.
Changes are written straight to the pack's Settings and take effect on the next Play. Everything the plugin does is an ordinary Studio edit, so Ctrl+Z works on all of it.
Updating without losing your settings
Drag the newer .rbxm in and press Update. The plugin snapshots your current Settings, installs the new version, and writes your values back over the new defaults.
- A setting the new version no longer has is reported as orphaned rather than dropped in silence, so you can see what changed.
- A setting whose name contains a dot is the one case the snapshot skips. Nothing shipped is named that way; if you added one yourself, rename it before updating.
Registry and Update Checks
On each refresh the plugin fetches a remote registry listing every available pack and its latest version. That powers two things:
- Catalogue listing. Packs you do not have yet appear in a separate "More from RBLX Essentials" section, so you can find them.
- Plugin update banner. If a newer plugin is out, a banner appears at the top.
The fetch is non-blocking: your local packs render immediately and the list fills in when the fetch returns, so the plugin is usable offline.
Kernel Coexistence
Every pack ships with its own copy of the shared runtime embedded inside its container. There is no separate Ecosystem Core to install first, and no install order to remember.
With several packs installed, the plugin keeps the newest copy in sync across all of them, and only ever moves forward: installing an older pack never puts an older runtime back. That is why updating any one pack brings the newest runtime to everything you have.
Uninstalling
Removes the pack from every service it was installed into. Its Settings go with it, so copy them somewhere first if you have spent time on them. Packs that were using it do not break; whatever they used it for simply goes quiet.
Guides
Short answers to the things people actually want to do. Each one is written for someone who has the plugin installed and a pack or two in their place, and none of them assumes you want to read the pack's source.
Where everything lives
Every pack installs into the same five places, always under a folder called RBLXEssentials. Nothing is ever scattered anywhere else, so if you cannot find something, it is in one of these.
ServerScriptService.RBLXEssentials
├── Kernel -- the one Script in the ecosystem
└── Packs.<PackId> -- server code, CustomRewards, Triggers
ReplicatedStorage.RBLXEssentials
├── Kernel -- shared modules and Remotes
└── Packs.<PackId>
├── manifest -- id, version, what it depends on
├── Client -- client code
├── Settings -- everything the plugin edits
└── Assets -- templates the pack clones at runtime
StarterPlayer.StarterPlayerScripts.RBLXEssentials
└── KernelClient -- the one LocalScript
StarterGui.RBLXEssentials.<PackId> -- the interfaces you edit
SoundService.RBLXEssentials -- SFX and Music
Workspace.RBLXEssentials.<PackId> -- demo parts, leaderboards, eggs
There is one Script and one LocalScript in the whole ecosystem, whatever number of packs you install. Everything else is a ModuleScript that the kernel boots in dependency order.
How do I edit a pack's interface?
Open StarterGui > RBLXEssentials > <PackId> and edit it like any other GUI in your place. It is real StarterGui content, not something built at runtime, so what you see in Studio is what a player gets. Change colours, fonts, corners, images, sizes, move things around: none of it needs code, and none of it needs the plugin.
Three rules keep a redesign working:
- Do not rename what the pack drives. Each pack's page lists the handful of names it fills in, like a Codes panel's
TextBoxor a day tile'sAmount. Everything else is yours. Rename one of those and the pack warns in the Output instead of breaking. - Size and position in scale, not offset. The whole kit is built that way, so an element is the same fraction of the screen on a phone as on a 4K monitor. Drag with the scale handles.
- Outlines are
ScaledSize. AUIStrokewithStrokeSizingMode = ScaledSizetakes itsThicknessas a fraction of the element's height, so a resized card keeps its border weight. If you add your own, match that.
Two packs make this easier still. General UI Kit's menus and Notifications System's templates each have a pencil beside their name in the plugin: click it and that panel or card appears on screen and is selected in the Explorer, so you can design it in place instead of hunting for it. Click it again when you are done, and the plugin will tell you if you left one visible, because Visible is saved with your place.
Two packs providing the same menu
Several packs ship their own copy of a menu so they work on their own. When General UI Kit is installed, its copy usually wins and the pack drives that one instead, so you get one menu rather than two.
Which copy wins is the AdoptPriority attribute on the panel: highest number wins, and the default is 0. If you have restyled the wrong copy and want yours to be the one used, raise its AdoptPriority.
How do I make a button do something of my own?
Set the button's Action attribute to a name, then add a function under that name in General UI Kit's CustomHandlers module:
-- ReplicatedStorage.RBLXEssentials.Packs.GeneralUIKit.CustomHandlers
-- Updates never overwrite this file.
return {
Rebirth = function(button)
-- runs when a button whose Action says "Rebirth" is clicked
print(button.Name, "was pressed")
end,
}
Opening and closing panels, prompting a game pass or a developer product, and inviting friends are already handled, so a handler of your own is only for the parts that are actually yours. The button object is passed in, so you can read your own attributes off it and use one handler for several buttons.
How do I add a new currency, and show it?
Four steps across four packs, none of them code.
- Save it. In the plugin's Datastore panel, under Default Data, open the
Statsgroup and press + Variable. Name itGems, pick N for number, press Add. The preview underneath shows you exactly what a new player will now receive. Its path isStats.Gems, and that string is what everything else asks for. - Show it on the HUD. Add a TextLabel to your GUI, tag it
UI_DataBind, and give it aDataPathattribute ofStats.Gems. AddPrefixorSuffixfor a symbol, andFormatforDefault,CommasorShort. It follows the value for the rest of the session with no script. - Rank it. In Easy Leaderstats, add a leaderstat whose data key is
Stats.Gemsto put it in the Tab menu, or point a leaderboard at it to put it on a board in the world. - Give it out. Any reward slot in Roulette, Time Rewards or Codes takes
Type = StatandTarget = Stats.Gems. So a code, a spin and a daily reward can all pay in Gems without anything being wired between them.
A key you add reaches players who already exist, on their next load. A key you remove stays in their saved data, so deleting one here can never lose what someone earned.
How do I give a reward the pack doesn't offer?
Roulette System, Time Rewards System and Codes System all take the same kind of module. Drop a ModuleScript into that pack's CustomRewards folder under ServerScriptService > RBLXEssentials > Packs > <PackId>:
local MyReward = {}
-- `d` is the live save inside a data update: what you change here
-- is persisted and replicated for you. `amount` is the slot's Amount.
function MyReward.Grant(player, d, amount)
d.Stats = d.Stats or {}
d.Stats.Gems = (d.Stats.Gems or 0) + amount
end
return MyReward
Then set that slot's Type to Custom and pick your module from the Module dropdown, which lists whatever is in the folder. A slot pointing at a module that is not there is switched off at startup with a warning, rather than failing the first time a player earns it.
You are not limited to numbers. A Grant can hand out a tool, teleport someone, or set a flag your own code reads later.
How do I add a sound, or a music track?
Sounds live in SoundService > RBLXEssentials > SoundSystem, under SFX and Music. There are two ways in:
- From the plugin. The Sounds panel has + Add sound: paste an asset id or a marketplace link, give it a name, press Add. That name is what you pass to
SFX:Play, and what the dropdowns elsewhere in the plugin will offer you. - From the Explorer. Drop a
Soundinto either group. It is picked up immediately, including while the game is running.
Then play it from anywhere on the client:
local SFX = require(game.ReplicatedStorage.RBLXEssentials.Kernel.SFX)
SFX:Play("Press")
SFX:PlayOverlap("Gold_Pickup") -- for things that fire fast
While you are in that panel: Measure all reads every sound's real loudness in decibels and Match all pulls the outliers to your target, which is the fastest way to stop one effect drowning out the rest.
How do I react to something happening?
When a player's numbers change
The save is mirrored onto the player as ordinary Value objects, so you can watch one from a LocalScript with no remotes:
local player = game.Players.LocalPlayer
local data = player:WaitForChild("PlayerData")
local cash = data:WaitForChild("Stats"):WaitForChild("Cash")
cash.Changed:Connect(function(value)
print("cash is now", value)
end)
A DataReady BoolValue sits in the same folder and turns true once the save has arrived, if you would rather wait for the whole thing than for one key.
When somebody buys something
local hook = game:GetService("ServerScriptService").RBLXEssentials
.Packs.DatastorePurchaseSystem:FindFirstChild("PurchaseCompleted")
if hook then
hook.Event:Connect(function(player, kind, id, currencySpent)
print(player.Name, "bought", kind, id)
end)
end
It fires after the purchase is recorded as granted, and it also fires for FreeMode grants, so you can test the whole path in Studio without spending Robux.
When you want to say something
local Notify = require(game.ReplicatedStorage
.RBLXEssentials.Packs.NotificationsSystem.Notify)
Notify.Toast(player, "TitleSubtitle", { Title = "Level up!", Subtitle = "You reached level 10" })
Notify.BannerAll({ Title = "Boss event", Subtitle = "The arena opens in 30 seconds" })
How do I update a pack without losing my settings?
Drag the new .rbxm into your place and press Update on that pack's card. The plugin takes a snapshot of your current Settings first, installs the new version, and writes your values back over the new defaults.
Two things worth knowing:
- A setting the new version no longer has is reported as orphaned rather than silently dropped, so you can see what changed.
- A setting whose name contains a dot is the one case the snapshot skips. None of the shipped settings are named that way; if you added one yourself, rename it before updating.
Updating one pack updates the shared runtime for all of them. The kernel travels inside every pack and only ever moves forward, so whichever pack you update brings the newest one to everything installed. Installing an older pack never puts an older kernel back.
How do I remove a pack?
Uninstall on its card removes it from every service it was installed into. Its Settings go with it, so if you have spent time configuring it, copy the Settings folder somewhere first.
Packs that depend on it keep working in the sense that they will not error, but whatever they used it for goes quiet: a pack that plays a sound goes silent, a pack that shows a notification shows nothing. The one exception is Datastore & Purchase System, which is a hard requirement for the packs that save anything.
Something isn't showing up
| What you see | Usually |
|---|---|
| No HUD button for a pack you installed | Another pack owns the HUD and this one's copy is hidden as a duplicate. Look for its button on General UI Kit's rail before adding a second one. |
| Two of the same button | Two packs shipped the same menu and neither has a higher AdoptPriority. Raise it on the one you want. |
| A panel opens empty | Something the pack fills by name has been renamed. The Output says which. |
| No sound anywhere | Sound System is not installed. Every pack asks for sound whether or not it is there and gets silence if it is not. |
| No notifications from any pack | Same shape: Notifications System is not installed. |
| A reward slot does nothing | Its Type is Stat or Custom with an empty Target, so it was switched off at startup. The Output names the slot. |
| A player joins with no data | They are told to rejoin instead of starting empty. Their real save is untouched. |
Almost everything above prints a warning in the Output window when it happens. Nothing in the ecosystem fails silently on purpose.
Datastore & Purchase System
PackId: DatastorePurchaseSystem · Version: 2.1.0 · Requires: nothing
What It Does
This is where your players' progress lives, and the backbone every other pack saves through. There is one datastore for your whole game, so a code, a spin and an egg all pay into the same numbers with nothing wired between them.
- Loads on join, saves on a timer, on leave and on shutdown.
- An in-memory cache, so every read mid-session is instant.
- Live replication to the client as Value objects, so showing a stat needs no remotes.
- Game pass checks, cached and remembered in the save.
- Developer product receipts handled safely: a receipt Roblox retries is confirmed rather than granted twice.
- Retries with backoff, request-budget awareness and save smoothing, so a full server does not spend its budget in one burst.
- Studio safety: a separate datastore version for testing, synthetic user ids for test clients, and one calm warning instead of error spam on an unpublished place.
Plugin Configuration
Default Data
This is what a new player starts with. Groups hold variables, and a variable is a number, a string or a true/false. + Variable and + Group add them; the red cross removes them.
The path you build here, Stats.Cash or Inventory.Slots, is the exact string every other pack asks for: a reward slot's Target, a leaderstat's data key, a label's DataPath. Add a key here and it appears in those pickers immediately.
A new key reaches players who already exist, on their next load: each saved profile is merged with the current defaults and anything missing is filled in. A changed default only affects new players. A removed key stays in profiles that already have it, so deleting one here can never lose what someone earned.
See exactly what a new player gets
The panel prints the finished save before you press Play, your own keys merged with the ones the packs add. It is selectable text, so it is also the fastest way to check a key is spelled the way your code spells it.
Datastore Settings
| Setting | Ships as | What it does |
|---|---|---|
DataStoreVersion | "v1" | Key suffix for the live datastore. Change it to start every player on a fresh slate. |
StudioDataStoreVersion | "v4" | The suffix used while testing in Studio, kept separate so a playtest can never touch live player data. |
AutoSaveInterval | 90 | Seconds between periodic saves per player, smoothed and jittered to respect the datastore budget. |
Wipe Studio Data | button | Deletes every key in the current Studio version, with a confirm step. A clean slate for testing. It never touches live data, and it needs a published place with Studio API access on. |
Purchase Settings
FreeMode makes every pass read as owned and lets a product grant itself, with no Robux and no prompt. It is how you test a purchase path fifty times in an afternoon.
It ships enabled, for development. Turn it off before you go live.
Uninstalling
Removes the pack from every service. Saved player data is untouched: it lives in Roblox's datastore, not in your place.
That's everything from the plugin.
Below: reading and writing a save from code, showing values on the client, and handling purchases.
Getting the API
Every pack boots through the kernel and receives the others' APIs through ctx.require. Declare the dependency in your own pack's manifest and ask for it in init:
-- ReplicatedStorage.RBLXEssentials.Packs.MyPack.manifest
return { id = "MyPack", version = "1.0.0", deps = { "DatastorePurchaseSystem" } }
-- ServerScriptService.RBLXEssentials.Packs.MyPack
return function(ctx)
local ds = ctx.require("DatastorePurchaseSystem")
end
The kernel guarantees this pack has finished booting before your init runs, so ctx.require never waits. Making a pack of your own is one folder and a manifest; the walkthrough is in the kernel reference that ships with the source.
Data API
ds.Get(player)
The player's whole save. The first call loads and caches; every call after that returns the cache instantly. Concurrent first calls are deduplicated, so everyone gets the same table.
local data = ds.Get(player)
print(data.Stats.Cash)
ds.Update(player, mutator)
Applies a change and pushes it to the client replica. The mutator gets the table directly.
ds.Update(player, function(data)
data.Stats.Cash += 100
end)
It persists on the next save and when the player leaves. Change the table only through Update, or through the helpers below which call it, so the replica stays in step.
ds.GetValue · ds.SetValue · ds.Increment
Path-addressed shortcuts for the common cases. The path is the same text the plugin shows.
local cash = ds.GetValue(player, "Stats.Cash") -- nil if the key is not there
ds.SetValue(player, "Nickname", "Ana") -- creates missing groups on the way
ds.Increment(player, "Stats.Cash", 250) -- returns the new value
ds.Increment(player, "Stats.Cash", -500, 0) -- the last argument is a floor
Increment treats a missing key as 0 and refuses a key holding something other than a number. Both it and SetValue return (result, err) and warn on failure.
ds.Save(player)
Queues an early save ahead of the schedule, for the moments that matter. It returns immediately; the write happens in the background, respecting the budget.
ds.GetSafeUserId(player)
The id the pack keys data by. In Studio, a test client with no real UserId gets a stable synthetic one, so test data never collides with production keys. Use it whenever you key your own stores by player.
ds.SaveAllImmediate(maxWaitPerWrite?) · ds.IsFreeMode()
The first flushes every cached profile now, which the pack already does for you on shutdown. The second is true while FreeMode is on.
Reading data on the client
Every player's save is mirrored under their Player as Folders and Value objects, updated on every write. No remotes.
-- LocalScript
local player = game.Players.LocalPlayer
local playerData = player:WaitForChild("PlayerData")
playerData:WaitForChild("DataReady") -- true once the first replication landed
local cash = playerData.Stats.Cash
cashLabel.Text = tostring(cash.Value)
cash:GetPropertyChangedSignal("Value"):Connect(function()
cashLabel.Text = tostring(cash.Value)
end)
Numbers, strings and booleans arrive as NumberValue, StringValue and BoolValue; arrays arrive as children named [1], [2] and so on. The replica is read-only by convention: writing to these Values does not write back to the server.
If all you want is a label that shows a number, you do not need any of this. Tag it UI_DataBind and give it a DataPath, and General UI Kit does it for you.
Purchases
ds.HasPass(player, passId)
Checks the in-memory cache, then the player's save, then Roblox, persisting a confirmed ownership back to the save. Returns true for everyone while FreeMode is on, and the cache clears itself when someone buys a pass mid-session.
if ds.HasPass(player, 123456789) then
-- give them the perk
end
ds.registerHandler(productId, callback)
One handler per developer product. It receives (player, receiptInfo) and must return true to confirm. Returning false or erroring makes Roblox retry later, so nothing is lost.
ds.registerHandler(987654321, function(player, receipt)
ds.Increment(player, "Stats.Cash", 1000)
return true
end)
Every granted receipt is remembered in the player's save, so a receipt Roblox re-delivers is confirmed without running your handler a second time. Product and pass ids are not stored by this pack: keep them where your own code needs them.
ds.GrantProduct(player, productId) · ds.InvalidatePass(player, passId)
The first runs a registered product handler with no real purchase, and only while FreeMode is on, which is how you test a reward flow in Studio. Your handler receives nil for receiptInfo on that path. The second clears a cached pass result; mid-session purchases already do it for you.
Knowing when somebody bought something
The pack publishes a BindableEvent anyone can listen to, without declaring a dependency:
local hook = game:GetService("ServerScriptService").RBLXEssentials
.Packs.DatastorePurchaseSystem:FindFirstChild("PurchaseCompleted")
if hook then
hook.Event:Connect(function(player, kind, id, currencySpent)
print(player.Name, "bought", kind, id)
end)
end
kind is "product" or "gamepass", and it fires after the purchase is recorded as granted. It fires for FreeMode grants too, so the whole path is testable in Studio. Notifications System uses this exact hook, which is why neither pack declares the other.
What a save looks like
{
Meta = {
SchemaVersion = 1
-- plus, at runtime: FirstJoinDate, SessionStart,
-- SessionId, LastSaveDate, SaveCount
},
Stats = {
Cash = 0,
Wins = 0
},
Pets = {
Equipped = {},
Owned = {}
},
Purchases = {
Gamepasses = {},
ReceiptHistory = {} -- filled as products are bought
}
}
Stats, and anything else you add, comes from Default Data above. Meta, Pets and Purchases are added by the packs unless you define them yourself.
When a save cannot be read
A player whose data fails to load is not dropped into the game with an empty one. They are told to rejoin, and their real save is still there when they do. This is the one case where doing nothing would quietly cost someone their progress.
Easy Pet System
PackId: EasyPetSystem · Version: 2.1.0 · Dependencies: Datastore & Purchase Sound System (optional)
What It Does
Easy Pet System is a complete, production-ready pet system for Roblox. Players hatch pets from eggs, collect them across rarity tiers, merge duplicates into rarer versions, equip pets that follow them around the map, and earn multiplier bonuses.
- Weighted rarity rolls for egg hatching (single and triple hatch).
- Auto-hatch gamepass support.
- Triple-hatch gamepass support.
- Extra Pet Space gamepass support.
- Three pet movement modes — Ground, Float (orbit), and Follow — switchable at runtime.
- Merge 3 pets of the same type into the next rarity tier.
- Merge All: auto-merges all eligible sets in one call.
- Equip Best: automatically equips the highest-multiplier combination.
- Multiplier API: sum or product of all equipped pet multipliers, accessible from any server script.
- 28 included pet models across 5 rarities (fully replaceable).
- BillboardGui egg hatching animations.
Plugin Configuration
Required: this pack uses Roblox DataStore. Go to Game Settings → Security → Enable Studio Access to API Services before testing, otherwise player data will not save.
All settings are managed directly from the plugin, no script editing needed.
Purchase Settings
Lists the game passes used by the pack. Each entry has a warning to replace the default ID with your own from the Roblox Creator Dashboard.
- ExtraPetSpace: allows players to equip a 4th pet.
- AutoHatch: allows players to auto-hatch eggs continuously.
- TripleHatch: allows players to hatch 3 eggs at once.
Before testing: create these Game Passes in the Roblox Creator Dashboard for your experience and paste each ID into the corresponding field. Without real IDs, ownership checks won't work.
Pet Data
- MaxOwned: maximum number of pets a player can have in their inventory.
- MaxEquipped: maximum number of pets a player can have equipped at once (increased to 4 with the ExtraPetSpace pass).
- MergeCount: number of pets of the same type required to perform a merge.
- ProximityDistance: stud distance within which pets stay near the player.
- FollowMode: how pets move relative to the player. Options:
Ground,Orbit,Follow.Orbitis labelled "Float" in the in-game Settings panel.
Rarities
Rarities are defined in ReplicatedStorage > RBLXEssentials > EasyPetSystem > PetSystem > PetData > Rarities. You can add, remove, and rename rarities freely. Pets reference rarities by name.
| Value | Type | Controls |
|---|---|---|
order | number | Tier rank used for pet sorting in the Pets menu and for determining the merge target (next rarity up). Lower = weaker. |
frameColor | Color3 | Border/stroke colour of each pet slot in the egg billboard GUI, and the probability percentage text colour. |
textColor | Color3 | Pet name label text colour in the egg billboard GUI. |
frameColor2 | Color3 | Read by PetConfig but not currently applied anywhere. Reserved for future use. |
labelColor | Color3 | Read by PetConfig but not currently applied anywhere. Reserved for future use. |
These values are the ones that render. Each card's stroke takes the rarity's MainColor, its gradient runs MainColor to SecondaryColor, and the pet's name takes MenuTextColor, applied when the menu is built. The colours baked into the shipped templates match these, so the editor shows you what a player will see, but the data is what wins.
Pet Card Templates
The pet cards in the Pets menu are cloned from one template per rarity. With General UI Kit installed they live in its menu; without it, in the pack's own copy:
StarterGui > RBLXEssentials > GeneralUIKit > GeneralUIKitGUI
> Menus > Pets > Container > PetContainer > Container
Each template is a Frame named [Rarity]PetTemplate (e.g. CommonPetTemplate, EpicPetTemplate) and tagged in CollectionService with the same name. When a pet card is rendered, the system resolves the correct template by tag:
CollectionService:GetTagged(petDef.rarity .. "PetTemplate")
The matching template is cloned and filled with that pet's data, then recoloured from the rarity's own values. Restyle a template freely: shape, corners, layout and fonts are yours, and the three colour slots are overwritten from the rarity so the card always matches the rest of the pack.
To add a new rarity, create a new Frame in the container, name it [NewRarity]PetTemplate, and apply the CollectionService tag [NewRarity]PetTemplate to it.
Pets
The full list of pets in the game. Each pet has a preview image (the 3D model), a name, a rarity, and a multiplier value. You can add new pets, delete existing ones, or change any of their properties here.
Eggs
Defines all eggs available in the game. Each egg has a name, a price, a currency (Cash or Robux), and a drop percentage table listing which pets can be hatched from it and at what probability. The total must add up to 100%.
At the bottom you can create a new Cash Egg or a Robux Egg.
Robux Eggs
Robux eggs use robuxPrice (the display price in Robux) and a productId (the Developer Product ID from the Roblox Creator Dashboard) instead of a cash price.
Uninstalling
Removes the pack from every service it was installed into, demo eggs included. Saved pets are untouched: they live in the player's data, not in your place.
That's everything from the plugin.
If you're not planning to modify the pack's code or call its APIs from your own scripts, you can stop here. The sections below are for developers who want to integrate the system directly into their own code.
API
Declare "EasyPetSystem" in your own pack's manifest.deps and ask the kernel for it:
local pets = ctx.require("EasyPetSystem")
pets.GetTotalMultiplier(player, multiply?)
Returns the combined multiplier of all equipped pets.
-- Sum all equipped multipliers (e.g. 1 + 1.5 + 2 = 4.5)
local total = pets.GetTotalMultiplier(player)
-- Multiply all equipped multipliers (e.g. 1 * 1.5 * 2 = 3.0)
local product = pets.GetTotalMultiplier(player, true)
-- Apply to a reward
local reward = baseCash * pets.GetTotalMultiplier(player)
pets.HatchEgg(player, eggId, count, isAuto?)
Hatches one or three pets from the specified egg. Returns a result table.
local result = pets.HatchEgg(player, "Tier1Egg", 1)
if result.ok then
for _, pet in result.pets do
print(pet.id, pet.rarity, pet.multiplier)
end
end
Parameters:
eggId(string): must match a key in the Eggs configuration.count(number):1for single hatch,3for triple (requires TripleHatch pass).isAuto(bool, optional):truefor auto-hatch (requires AutoHatch pass).
Failure reasons: "invalid_egg", "insufficient_funds", "inventory_full", "need_triple_pass", "need_auto_pass", "cooldown".
Each pet in result.pets has: uid, id, rarity, multiplier, createdAt.
pets.EquipPet(player, uid)
Equips a pet by its unique ID. Players can have up to 3 pets equipped (4 with the ExtraPetSpace pass).
pets.UnequipPet(player, uid)
Unequips a pet by its unique ID.
pets.DeletePet(player, uid)
Deletes a pet from the player's inventory. The pet must be unequipped first.
pets.MergePets(player, petId)
Merges 3 pets of the same type into one pet of the next rarity tier. Non-equipped pets are consumed first; if there aren't enough, the remaining merge slots are filled from equipped pets, which are unequipped before being consumed.
local result = pets.MergePets(player, "Cat")
if result.ok then
print("Merged into:", result.pet.rarity)
end
Failure reasons: "no_next_tier", "not_enough", "invalid_pet".
pets.MergeAll(player)
Merges all eligible pet sets in a single call.
local result = pets.MergeAll(player)
print("Merges performed:", result.mergeCount)
pets.EquipBest(player)
Unequips all current pets and re-equips the combination with the highest total multiplier, up to the equip limit.
Configuration
Pets, eggs, rarities, and core limits are all data-driven — edit them in the Explorer or through the plugin's settings panel without touching code. Pet movement behaviour (radius, speed, bobbing) is tuned in one editable Lua module, PetVisualsConfig (see below).
Key Settings
| Setting | Default | Description |
|---|---|---|
MaxOwned | 50 | Maximum pets a player can hold in their inventory. |
MaxEquipped | 3 | Base number of pets a player can equip simultaneously. |
MergeCount | 3 | Number of pets required to perform a merge. |
FollowMode | "Ground" | How pets move relative to the player. Options: "Ground", "Orbit", "Follow". Orbit is labelled "Float" in the in-game Settings panel. |
ProximityDistance | 7 | Stud distance within which pets stay near the player. |
Switching Follow Mode at Runtime
The active movement mode is controlled by a PetFollowMode attribute on the local player. Set it from any LocalScript — the Settings panel does exactly this:
local Players = game:GetService("Players")
Players.LocalPlayer:SetAttribute("PetFollowMode", "Orbit") -- "Ground", "Orbit", or "Follow"
Pets react immediately; no rejoin or respawn required. If the attribute is unset, the mode falls back to PetVisualsConfig.DefaultMode.
Tuning Pet Movement: PetVisualsConfig
ReplicatedStorage > RBLXEssentials > EasyPetSystem > PetSystem > PetVisualsConfig is an editable ModuleScript that defines every movement mode and its parameters:
return {
DefaultMode = "Ground",
Modes = {
Orbit = {
label = "Float", -- name shown in the Settings panel
Radius = 4, -- studs from character center
Speed = 45, -- degrees per second
FloatHeight = 2.5, -- studs above HumanoidRootPart
BobAmount = 0.25, -- vertical bob amplitude (studs)
BobSpeed = 1.2, -- bob frequency (Hz)
LerpAlpha = 0.15,
},
Ground = {
label = "Ground",
Radius = 4,
Speed = 35,
FloatHeight = -2, -- floor level relative to HumanoidRootPart
BobAmount = 0,
BobSpeed = 0,
LerpAlpha = 0.15,
},
Follow = {
label = "Follow",
Spacing = 2.5, -- studs between each pet in the trail
FloatHeight = -2,
LerpAlpha = 0.1,
},
},
}
Adjust radius, orbit speed, float height, bobbing, or trail spacing here to change how equipped pets move.
Reading Configuration in Code: PetConfig
ReplicatedStorage > RBLXEssentials > EasyPetSystem > PetSystem > PetConfig loads the full PetData tree into a plain Lua table, usable from both client and server:
local PetConfig = require(ReplicatedStorage.RBLXEssentials.EasyPetSystem.PetSystem.PetConfig)
print(PetConfig.MaxOwned, PetConfig.MaxEquipped, PetConfig.MergeCount)
print(PetConfig.ProximityDistance, PetConfig.FollowMode)
local catDef = PetConfig.Pets["Cat"] -- { name, rarity, multiplier }
local eggDef = PetConfig.Eggs["Tier1Egg"] -- { name, price, currency, pets, robuxPrice?, productId? }
local rarity = PetConfig.Rarities["Epic"] -- { order, frameColor, frameColor2, textColor, labelColor }
PetConfig.getNextRarityPets(petId) returns the merge candidates for a pet — the { id, percent } entries from the same egg at the next rarity tier — or nil if the pet is already the highest rarity in its egg:
local candidates = PetConfig.getNextRarityPets("Cat")
if candidates then
for _, entry in candidates do
print(entry.id, entry.percent)
end
end
Gamepasses
Configure these pass IDs in the plugin settings to enable the corresponding features:
| Gamepass | Effect |
|---|---|
TripleHatch | Allows hatching 3 eggs at once. |
AutoHatch | Allows continuous auto-hatching. |
ExtraPetSpace | Increases the equip limit from 3 to 4. |
Client Integration: Remotes
On startup, the Bootstrap server script publishes the client-facing surface under ReplicatedStorage > PetSystem:
PetSystem > Remotes
| Remote | Type | Invoked with |
|---|---|---|
BuyAndHatchEgg | RemoteFunction | { eggId = "Tier1Egg", count = 1, isAuto = false } |
GrantFreeProduct | RemoteFunction | — |
EquipPet | RemoteFunction | pet uid |
UnequipPet | RemoteFunction | pet uid |
DeletePet | RemoteFunction | pet uid |
MergePets | RemoteFunction | petId |
EquipBest | RemoteFunction | — |
MergeAll | RemoteFunction | — |
PremiumHatchResult | RemoteEvent | fired server → client with Robux hatch results |
PetSystem > PassConfig — a folder the built-in UI reads for pass gating: NumberValues TripleHatch, AutoHatch, ExtraPetSpace (holding the pass IDs) and a BoolValue FreeMode (when true, all pass checks are bypassed).
Use these remotes to trigger pet actions from your own UI:
local Remotes = ReplicatedStorage:WaitForChild("PetSystem"):WaitForChild("Remotes")
local result = Remotes.BuyAndHatchEgg:InvokeServer({
eggId = "Tier1Egg",
count = 1,
isAuto = false,
})
if result.ok then
print("Hatched:", result.pets[1].id)
end
Adding Egg Models to the Map
Egg models in Workspace are discovered by tag, so you can place as many as you like. Each egg Model must:
- Be tagged
Eggin CollectionService. - Have an
EggIdstring attribute matching a key in the Eggs configuration (e.g."Tier2Egg"). - Contain a part named
Guiholding aBillboardGuitaggedEggBoard— this is the hatch interface players interact with.
The simplest workflow: duplicate one of the included egg models, change its EggId attribute, and swap the mesh. The billboard, proximity detection, and hatch buttons wire themselves up automatically.
Hatch Toast
The hatch-reveal toast (the full-screen pet reveal after hatching) is exposed as a client module at ReplicatedStorage > RBLXEssentials > EasyPetSystem > PetSystem > HatchToast, so you can trigger it from your own flows:
local HatchToast = require(ReplicatedStorage.RBLXEssentials.EasyPetSystem.PetSystem.HatchToast)
if not HatchToast.IsBusy() then
HatchToast.ShowSingle(petData) -- one pet reveal
-- or
HatchToast.ShowTriple(pets) -- three-pet reveal (triple hatch)
end
HatchToast.IsBusy()— returnstruewhile a reveal is currently playing.HatchToast.ShowSingle(petData)— shows the reveal for one pet (a pet table withid,rarity, etc.).HatchToast.ShowTriple(pets)— shows the three-slot reveal for an array of pets.
Included Pets
28 pets across 5 rarities sourced from a Roblox asset pack. All pet models are fully replaceable, you can add, remove, or swap them at any time without code changes.
| Rarity | Multiplier Range |
|---|---|
| Common | x1 to x500 |
| Uncommon | x2 to x750 |
| Rare | x4 to x80,000 |
| Epic | x25 to x200,000 |
| Legendary | x400 to x1,500,000 |
Sound System & Music Player
PackId: SoundSystem · Version: 2.1.0 · Requires: nothing
What It Does
Sound System ships 42 sound effects, fifteen music tracks and seven music-player templates, including Studded. Every player has open and hide controls that preserve playback. Music and SFX use separate volume groups, so muting one leaves the other alone.
It is also what gives every other pack its sound. Every pack in the ecosystem asks for a sound whether or not this one is installed, and gets silence if it is not, so adding it later turns audio on across all of them at once, with nothing to wire.
Plugin Configuration
General and Music Player
| Setting | Default | What it does |
|---|---|---|
SFXEnabled | on | Global kill switch. With it off, every sound effect in the game is silent, for every player. |
MusicPlayerEnabled | on | With it off, the music player is hidden and no music plays. For games that do not want one. |
Template | Template 1 | Which of the seven templates the player uses. The panel previews the one you pick. |
The six original templates now include open and hide controls, and a seventh Studded template is included. They are ordinary frames in StarterGui: duplicate one, keep its MusicPlayerTemplate tag, and restyle it to add your own. The image below shows the original six designs.
Music
Fifteen songs ship. Each carries its own volume, so a track mastered louder than the rest can be pulled down on its own instead of turning the whole playlist down. The group volume above them moves all of them together.
SFX
42 effects ship, covering money, purchases, hovers, presses, level ups, pickups, impacts, movement and water. The 24 additions are optional sounds; they are not assigned to gameplay automatically. The plugin measures each one's loudness in decibels and flags the ones that miss your target with a Match button; Match all pulls every outlier into line at once.
WindLoop, UI_Confirm_01, Water_Splash_10 and Movement_Swish_04 are deferred from 2.1.0 because their current Roblox audio IDs could not be distributed.
+ Add sound takes an asset id, an rbxassetid:// string or a marketplace link, plus a name. That name is what you pass to SFX:Play, and what every sound dropdown elsewhere in the plugin will then offer you.
Uninstalling
Removes the pack from every service it was installed into. Every other pack goes silent rather than breaking.
That's everything from the plugin.
Below: playing sounds from your own code, the music player's structure, and the volume controls.
Where the sounds live
SoundService > RBLXEssentials > SoundSystem > SFX -- sound effects
SoundService > RBLXEssentials > SoundSystem > Music -- music tracks
They are edit-time content, not something built at runtime, so you can browse and replace them in the Explorer. Sounds are looked up by name: drop a Sound into the right group and it is available immediately, including one added while the game is running. Each sound's SoundGroup is assigned for you, so it obeys the player's volume without being told.
Playing a sound
From any LocalScript of your own:
local SFX = require(game.ReplicatedStorage.RBLXEssentials.Kernel.SFX)
-- Plays by name. Restarts if it is already playing.
SFX:Play("Press")
-- Clones it, so several can overlap. Each clone cleans
-- itself up when it finishes. Use this for anything that
-- can fire in quick succession.
SFX:PlayOverlap("Gold_Pickup")
This is the kernel's own sound facade, which every pack calls too. It is a silent no-op when Sound System is not installed, so code written against it never errors on a place that does not have the pack.
Pitch variation and owned loops
Inside a client pack module initialized by the Kernel, declare SoundSystem in manifest.deps, then obtain it with ctx.require("SoundSystem"). PlayOneShot(name, {volume = 0.7, pitch = 1.05}) plays a 2D copy without changing the template. PlayLoop(name, options) returns an owned loop: update its volume or PlaybackSpeed and call Destroy() when finished. Supply your own loop asset.
Both APIs may return nil while loading, muted or at their voice limits. Volume multiplies the template volume (0–2); pitch is absolute PlaybackSpeed (0.5–2). One-shots allow eight voices and expire after eight seconds; loops have a separate limit of two and no automatic eight-second expiry.
At a place in the world
Positional playback is on the pack's server API, reached through the kernel after declaring "SoundSystem" in your own pack's manifest.deps:
local sound = ctx.require("SoundSystem")
-- Everyone hears it, wherever they are.
sound.PlayAt("Money_1", part.Position)
-- With a range, it falls off with distance instead.
sound.PlayAt("Money_1", part.Position, 30)
-- Or for one player only.
sound.PlayAtForPlayer(player, "Gem_Pickup", part.Position, 30)
A ranged sound gets an invisible host part at that spot, rolls off with LinearSquare, and the part is destroyed when the sound ends. Without a range it plays globally, cloned straight into the SFX group. Either way it stays in that group, so the player's own volume and mute still apply.
Hearing all three before you write anything
Five parts ship in Workspace > RBLXEssentials > SoundSystem > Demo, each with a prompt on it and no code in it at all. Between them they cover every way of playing a sound: straight into your ear, out of a point in the world, and out of a point for one player only, the last two with and without a falloff range. Walk up, press the prompt, hear the difference, then delete the folder.
The music player
Drop your tracks into the Music group and the client does the rest: it builds the playlist, starts on a random track, advances when one ends, and keeps up if tracks are added or removed while the game runs.
The UI is tag-based. Tag any Frame with MusicPlayerFrame and give it this structure:
Frame (tagged MusicPlayerFrame)
└── Container
├── SongName (TextLabel)
├── Duration (TextLabel)
└── Buttons
├── Mute (ImageButton with an "Icon" child)
└── Next (ImageButton)
Everything is wired for you: the song's name, the elapsed and total time updated live, a mute that swaps its icon, and a skip. You can tag more than one frame, for example one on the HUD and one inside a settings panel. They share a single playback state, so muting in one updates every other instantly, and frames added or removed at runtime are picked up.
Volume controls
The pack registers six actions you can put on any button by setting its Action attribute. No code.
| Action | What it does |
|---|---|
ToggleMusicMute | Mutes and unmutes music, restoring the previous volume. |
ToggleSFXMute | The same for sound effects. |
MusicVolumeUp / MusicVolumeDown | Steps music volume. |
SFXVolumeUp / SFXVolumeDown | Steps SFX volume. |
Volume moves along a nine step ladder from 0.04 to 1.0, so each press is a change you can hear rather than a linear crawl. The handlers also drive optional feedback: a mute button can show an ON/OFF status, and a frame with a VolumeGroup attribute of "Music" or "SFX" holding Volume1 to Volume9 segments renders as a live volume bar.
Everything stays in step across surfaces, because the music player's mute and the settings menu's mute are moving the same SoundGroup.
The pack ships its own Settings panel and the button that opens it, so the volume controls exist even with no other pack installed. With General UI Kit present, its Settings panel wins and these controls appear there instead. One menu, whichever pack provided it.
General UI Kit
PackId: GeneralUIKit · Version: 2.1.0 · Requires: nothing
What It Does
General UI Kit is the menu system your game was going to need anyway, and the framework the rest of the ecosystem plugs into. Behaviour is declared with CollectionService tags and Instance attributes, so adding a panel or a button means tagging something, not editing a script.
It ships with fourteen menus and twelve HUD buttons already built, and provides:
- Panel management: open, close, exclusive mode, animated transitions.
- Button sounds, hover scaling and cursor-following tooltips.
- Decorative animations: rotation, hover bob, idle bob, colour cycling.
- An effect icon system with tooltip descriptions.
- Live data binding, so a label shows and follows a saved value with no script.
- Typing sounds on text boxes.
- A place for your own button actions that pack updates never overwrite.
How It Works
The kernel discovers tagged elements anywhere in the local PlayerGui, both at startup and as they arrive. Tags say what an element is; attributes say how it behaves. Tag a Frame UI_Panel, set PanelId = "Shop", and it is a panel.
Because discovery is by tag rather than by path, every pack's menus land in the same system. When two packs ship the same menu, the one with the higher AdoptPriority wins and the other is left alone, so you get one Shop rather than two.
Button clicks are dispatched by action name. The built-in actions are handled for you; anything else goes to your own CustomHandlers module.
Plugin Configuration
Menus
Every menu in your place, with the button that opens each one. Rename one, delete one, add one. Two of them are opened by other packs from code rather than by a button, and the list says so.
The pencil beside a menu makes it appear on screen and selects it in the Explorer, so you can design it in place instead of hunting for it. Click it again when you are done; the plugin warns you if you leave one visible, because Visible is saved with your place.
HUD Buttons
A button is six fields: what it is called, which menu it opens, its icon, whether it shows at all, whether it has a tooltip and what that tooltip says. Show in HUD hides a button without deleting it, which is how you keep a menu your own code opens while taking its button off the screen.
Uninstalling
Removes the pack from every service it was installed into. Packs that were using its menus fall back to their own copies.
That's everything from the plugin.
If you're not planning to modify the pack's code or call its APIs from your own scripts, you can stop here. The sections below are for developers who want to integrate the system directly into their own code.
Tag Reference
UI_Panel
A toggleable menu panel (Frame or ScreenGui).
| Attribute | Type | Required | Description |
|---|---|---|---|
PanelId | string | Yes | Unique identifier for this panel. |
Exclusive | bool | No | Default: true. When an exclusive panel opens, it closes every other open panel that is also exclusive. Set to false to let the panel stay open alongside others — non-exclusive panels are never auto-closed. |
Animation | string | No | Panel open animation. Default: "Bounce". See below for available animations. |
Panel animations. The PanelAnimations loader aggregates animation modules from every installed RBLX Essentials pack, so the available set grows with your installed packs:
"SpinReveal"— ships with General UI Kit."Bounce"(the default) — ships embedded with every pack's shared runtime, so it's always available.
A pack contributes more by putting a ModuleScript that returns function(menu: GuiObject) in its own Client/PanelAnimations/ folder. The module's name becomes the Animation attribute value.
UI_Button
A clickable button (TextButton or ImageButton) that dispatches an action.
| Attribute | Type | Required | Description |
|---|---|---|---|
Action | string | Yes | Action name. The built-in ones below are handled for you; anything else is dispatched to your CustomHandlers module on click. |
TargetPanel | string | No | The PanelId to act on. Only read when Action is TogglePanel or ClosePanel — it has no effect with other actions. |
ShowTooltip | bool | No | If true, shows the shared Button tooltip on hover (see UI_Tooltip). |
TooltipText | string | No | Text shown in the Button tooltip. Defaults to the button's Name. |
BuySound | bool | No | If true, plays the purchase sound instead of the standard click sound. |
Linking a button to a panel:
To open or close a panel from a button, set Action = "TogglePanel" and TargetPanel = "<PanelId>" on the button. No handler is needed; it is one of the built-in actions.
Example: a button that toggles the Shop panel.
- Panel frame: tag
UI_Panel, attributesPanelId = "Shop",Exclusive = true - Button: tag
UI_Button, attributesAction = "TogglePanel",TargetPanel = "Shop"
Use Action = "ClosePanel" (with the same TargetPanel) for a dedicated close button inside the panel. If you omit TargetPanel on a ClosePanel button, it closes the nearest ancestor panel — handy for a generic X button.
Icon hover effect: a direct child of the button named Icon scales up slightly on hover and back on leave. Nothing to tag.
Every GuiButton is wired, tagged or not, so a button of your own gets the click and hover sounds for free. Two tags opt out: UI_Toggle, which is handled separately, and UI_Blocker, for the invisible full-screen sheet a modal panel puts behind itself. A blocker is a button only so that it swallows clicks, so it gets no sound, no tooltip and no icon tween.
UI_Animated
A decorative UI element with a looping animation.
| Attribute | Type | Required | Description |
|---|---|---|---|
Animation | string | Yes | "Rotate" (a continuous spin), "HoverBob" (a float on hover), "SineBob" (a constant bob and rock) or "RainbowCycle" (cycles the background colour). |
Speed | number | No | Duration in seconds of one full rotation — larger values spin slower. Default: 15. Only used by Rotate; ignored by HoverBob. |
UI_Tooltip
A shared tooltip frame. There is one tooltip per type, registered by its TooltipType attribute — every UI_Button with ShowTooltip uses the single Button tooltip, and every UI_EffectIcon uses the single Effect tooltip. You do not create a tooltip per button.
| Attribute | Type | Required | Description |
|---|---|---|---|
TooltipType | string | Yes | "Button" (follows the cursor near a hovered button) or "Effect" (anchors beside a hovered effect icon, with screen-edge bounds checking). |
Tooltip content contract. The labels a tooltip fills in are found either by name or by tag, anywhere inside the tooltip frame:
| Tag | Where | Purpose |
|---|---|---|
ButtonName, by name | Inside the Button tooltip | TextLabel that receives the button's TooltipText, or its Name when none is set. |
GeneralUIKit_EffectNameLabel | Inside the Effect tooltip | TextLabel that receives the effect icon's Name. |
GeneralUIKit_EffectDescriptionLabel | Inside the Effect tooltip | TextLabel that receives the effect icon's Description. |
Icon, by name | A direct child of any button | Scales up on hover. |
UI_EffectIcon
An icon representing a buff or game effect. Hovering it shows the shared Effect tooltip with the icon's Name as the title and Description as the body.
| Attribute | Type | Required | Description |
|---|---|---|---|
Description | string | Yes | Text displayed in the Effect tooltip on hover. |
UI_TypeSound
Apply to a TextBox to play a typing sound as the player types. The sound plays only when the text gets longer — backspace and deletions are silent. No attributes required.
UI_DataBind
Apply to a TextLabel or TextButton to live-bind its text to player data. The label updates automatically whenever the bound value changes — no polling, no manual refresh.
| Attribute | Type | Required | Description |
|---|---|---|---|
DataPath | string | Yes | Dot-path into the replicated PlayerData folder, e.g. Stats.Cash. |
Prefix | string | No | Text prepended to the value, e.g. "$". |
Suffix | string | No | Text appended to the value, e.g. " pts". |
Format | string | No | Number formatting: Default (raw), Short (12.5K, 3.2M, 1.1B…), Full (floored integer), Commas (1,250,000). Non-numeric values are shown as-is. |
Example: a cash counter in your HUD.
- TextLabel: tag
UI_DataBind, attributesDataPath = "Stats.Cash",Prefix = "$",Format = "Short"
Your own button actions
Set a button's Action attribute to a name, then add a function under that name in the pack's CustomHandlers module. Updates to the pack never overwrite that file.
-- ReplicatedStorage.RBLXEssentials.Packs.GeneralUIKit.CustomHandlers
return {
OpenShop = function(button)
print(button.Name, "was pressed")
end,
BuyItem = function(button)
local productId = button:GetAttribute("ProductId")
game:GetService("MarketplaceService"):PromptProductPurchase(
game.Players.LocalPlayer, productId
)
end,
}
The button itself is passed in, so you can read your own attributes off it and drive several buttons from one handler. An entry whose name matches a built-in action replaces it.
Built-in Actions
These are handled for you. You do not need a handler for any of them.
| Action | Effect |
|---|---|
TogglePanel | Toggles the panel named in TargetPanel. |
ClosePanel | Closes the panel named in TargetPanel, or the button's nearest ancestor panel if TargetPanel is unset. |
InviteFriends | Prompts the SocialService friend-invite dialog (permission-checked; fails silently if invites aren't available). |
Notes
- Sound System is optional. Every sound this pack asks for goes through the kernel, which is silent until Sound System registers itself. Install it and the whole kit gains its clicks, hovers and typing sounds at once; leave it out and nothing errors.
- Every
GuiButtonis wired for click and hover sounds with no tag needed. To exclude one, such as an invisible click blocker, tag itUI_Blocker. - Tooltips are throttled at 16ms intervals to avoid layout thrashing.
- Every connection is cleaned up when the GUI is destroyed, so respawns and reloads leave nothing behind.
Easy Leaderstats & Leaderboards System
PackId: EasyLeaderstatsSystem · Version: 2.0.0 · Dependencies: Datastore & Purchase System
What It Does
Easy Leaderstats & Leaderboards System adds two related features to your game:
- Leaderstats: the in-game stat list shown in the Tab/escape menu. Values are read directly from player data (provided by the Datastore & Purchase System) and kept in sync automatically.
- Global Leaderboards: physical in-world boards that display the all-time top players for any stat, stored in Roblox
OrderedDataStores and refreshed on a configurable interval.
Both features are fully configuration-driven from the plugin. No script editing is needed to add or remove stats and leaderboards.
Plugin Configuration
Selecting the pack in the plugin opens a settings panel with two sections: Leaderstats and Leaderboards.
Leaderstats
Each stat entry corresponds to one row in the Tab menu. Entries are grouped by the data group they belong to (e.g. Stats).
| Field | Description |
|---|---|
DisplayName | Label shown in the Tab menu (e.g. Cash, Wins). |
Order | Sort order within the stat list. Lower numbers appear first. |
DataKey | Dot-path into PlayerData (e.g. Stats.Cash). Read-only; derived from Datastore Default Data. |
DisplayFormat | Optional formatting keyword applied to the raw number (see the keyword table below). If empty, Default is used. |
Prefix | Optional text prepended to the displayed value (e.g. $). |
Suffix | Optional text appended to the displayed value (e.g. wins). |
DisplayFormat accepts these keywords (not printf-style format strings):
| Keyword | Behaviour |
|---|---|
Default | Raw number, displayed as-is. |
Short | Abbreviated with a suffix tier: 1.5K, 2.3M — tiers go K, M, B, T, Qa, Qi. |
Full | Floored integer (52500.9 → 52500). |
Commas | Thousands separators (52500 → 52,500). |
These fields compose left-to-right: Prefix + formatted value + Suffix. When a Prefix or Suffix is set and the format is Default, the value is formatted as Full (a floored integer string) so the affix can wrap it.
| DisplayFormat | Prefix | Suffix | Raw value | Result |
|---|---|---|---|---|
Default | (empty) | (empty) | 52500 | 52500 |
Short | (empty) | (empty) | 52500 | 52.5K |
Short | (empty) | (empty) | 2300000 | 2.3M |
Commas | $ | (empty) | 52500 | $52,500 |
Default | $ | (empty) | 52500.9 | $52500 (treated as Full because a prefix is set) |
Full | (empty) | wins | 7 | 7 wins |
+ Add Stat creates a new entry. The DataKey is selected from the existing variables in your Datastore Default Data, so stats must be defined there first.
Leaderboards
Global Settings
| Setting | Default | Description |
|---|---|---|
RefreshInterval | 60 | Seconds between board refreshes. |
MaxEntries | 50 | Maximum rows shown per board. |
Leaderboards
Each entry drives one physical in-world leaderboard board. Boards are discovered via the CollectionService tag ELS_Leaderboard: any Model in workspace with that tag and an ELS_LeaderboardId attribute appears here automatically. Models can be placed anywhere in the workspace hierarchy.
| Field | Description |
|---|---|
StatPath | Dot-path into PlayerData for the stat this board tracks (e.g. Stats.Cash). Changing this renames the board automatically. |
Template | Which of the three designs this board uses, from a dropdown. Changing it swaps the model in the world while keeping its position and orientation. |
ValueFormat | How values are displayed: Raw (52500), Comma (52,500), or Abbreviated (52.5K). |
Enabled | When OFF, this leaderboard is skipped entirely. |
+ Add Leaderboard asks for a name, a stat path and a template, then clones that template into Workspace > RBLXEssentials > EasyLeaderstatsSystem, gives it its own id, and creates the matching Settings entry. The new model is selected in Studio for you, so you can drag it where it belongs.
Delete Leaderboard removes both the Settings entry and the workspace model.
Duplicating a Board in Studio
If you duplicate a leaderboard Model in Studio, the plugin detects the duplicate GUID and automatically assigns the clone a new unique ID and a new Settings entry.
Uninstalling
Removes the pack from every service it was installed into, and takes the demo boards out of the workspace with it.
That's everything from the plugin.
The sections below cover how it works underneath and what to expect at runtime.
How It Works
Leaderstats
A leaderstats folder is created under each player as they join, with one entry per configured stat. The value is read from that player's replicated save using the DataKey path, and a changed signal keeps it in step for the rest of the session. You never write to leaderstats yourself: change the saved value and the row follows.
The leaderstat type follows the source value in PlayerData:
| Source type | Leaderstat created |
|---|---|
IntValue / NumberValue | IntValue — or StringValue when a custom DisplayFormat, Prefix, or Suffix is set, so the formatted text can be displayed. |
StringValue | StringValue — Prefix/Suffix are still applied to the string. |
BoolValue | BoolValue — shown as true/false. |
Global Leaderboards
Stats are written to OrderedDataStores in three situations:
- On join: once PlayerData is loaded and all configured stat values are non-nil, stats are written. Polls up to 10 seconds waiting for data readiness.
- On leave: stats are written immediately when the player disconnects.
- Periodically: every 5 minutes, all current players' stats are written to keep leaderboard data fresh for long sessions.
Board UI is refreshed every RefreshInterval seconds. The first refresh runs 5 seconds after server start to allow player data to load. During each refresh, the top MaxEntries players are read from the OrderedDataStore and the SurfaceGui list on the in-world board is rebuilt. A countdown timer on each board shows when the next refresh will occur.
Per-board display settings have sensible fallbacks, all derived from the last segment of StatPath (e.g. Stats.Cash → Cash):
- Empty
TitleLabelText→TOP <STAT>(e.g.TOP CASH). - Empty
VariableNameText→<STAT>(e.g.CASH). - Empty or unrecognized
ValueFormat→Raw.Abbreviatedcovers the K, M, and B tiers.
OrderedDataStore Naming
Store names are derived automatically from the stat path: GlobalLeaderboard_ followed by the dot-path with dots replaced by underscores.
-- e.g. Stats.Cash → GlobalLeaderboard_Stats_Cash
Inspecting the raw store: values are stored with a +1 offset (subtracted back on read), so a stored 52501 means the player's stat is 52500. Stats ≤ 0 are not written to the store at all.
Dummy Avatars
The top three players on each board wear their real Roblox avatar on Dummy1, Dummy2 and Dummy3 beside it, playing a looping emote.
The scale comes from the rig itself. Each template builds its podium dummies at its own size, and the pack measures that before applying anything, so an avatar lands at the size the template was designed for rather than at one number shared by every board. A podium you scaled yourself is measured the same way and keeps working.
A rank whose player has not changed since the last refresh is left alone, so only a genuine change costs a GetHumanoidDescriptionFromUserId call.
Settings Structure
All settings live under ReplicatedStorage > RBLXEssentials > Packs > EasyLeaderstatsSystem > Settings, and the plugin is the way to edit them.
Settings/
├── Leaderstats/
│ └── [GroupName]/
│ └── [StatName]/ — one folder per stat
│ ├── DataKey StringValue dot-path into PlayerData (read-only)
│ ├── DisplayName StringValue label shown in Tab menu
│ ├── Order NumberValue sort order
│ ├── DisplayFormat StringValue Default | Short | Full | Commas
│ ├── Prefix StringValue optional prefix text
│ └── Suffix StringValue optional suffix text
└── Leaderboards/
├── RefreshInterval NumberValue seconds between refreshes (default: 60)
├── MaxEntries NumberValue max rows per board (default: 50)
└── [EntryName]/ — one folder per leaderboard
├── StatPath StringValue dot-path into PlayerData
├── Template StringValue name of template in Templates/ folder
├── Enabled BoolValue if false, board is skipped
├── ValueFormat StringValue Raw | Comma | Abbreviated
├── TitleLabelText StringValue text shown on TopPart title label
└── VariableNameText StringValue column header text on the board list
The three board designs live beside Settings, in the pack's own Assets folder. They are the catalogue + Add Leaderboard clones from; nothing reads them at runtime.
ReplicatedStorage.RBLXEssentials.Packs.EasyLeaderstatsSystem
├── Settings/
└── Assets/
└── Templates/
├── LeaderboardTemplate1 [Model]
├── LeaderboardTemplate2 [Model]
└── LeaderboardTemplate3 [Model]
Each leaderboard Settings folder also has an ELS_LeaderboardId attribute (GUID string) that links it to its workspace model.
In-World Board Setup
A board is any Model in the workspace tagged ELS_Leaderboard, wherever you put it. New ones are cloned from the design named in the board's Template setting and dropped into Workspace > RBLXEssentials > EasyLeaderstatsSystem for tidiness, which is created if it is not there. Changing Template later swaps the model in place, keeping its position and orientation.
Copy a board in Studio and it is found. Boards are discovered by tag rather than from a list you maintain, so a duplicate gets a new id and its own Settings entry, and you point it at a different stat.
The system locates UI elements by ELS_Role attribute, not by path, so the internal hierarchy of the model can vary between templates.
[StatPath leaf] Leaderboard [Model]
├── BoardModel
│ ├── BoardBase [Part]
│ │ └── Leaderboard [SurfaceGui]
│ │ ├── Rank [TextLabel, header] ELS_Role = HeaderRank
│ │ ├── Name [TextLabel, header] ELS_Role = HeaderName
│ │ ├── Value [TextLabel, header] ELS_Role = HeaderValue (shows VariableNameText)
│ │ └── List [ScrollingFrame] ELS_Role = List
│ │ └── Template [Frame, cloned per entry, must stay hidden]
│ │ └── Frame
│ │ ├── Rank, Name, Value [TextLabel]
│ │ └── Icon [ImageLabel]
│ └── TopPart [Part]
│ └── Leaderboard [SurfaceGui]
│ └── TextLabel ELS_Role = TitleLabel (shows TitleLabelText)
├── Dummy1 / Dummy2 / Dummy3 [Model, R15 rigs for top-3 avatars]
└── Timer [Part]
└── GUI [SurfaceGui]
└── Label [TextLabel, shows "Refresh in: Xs"]
ELS_Role | Element | Populated with |
|---|---|---|
TitleLabel | TextLabel on TopPart board | TitleLabelText setting (e.g. TOP CASH) |
List | ScrollingFrame containing entry rows | (used as parent for cloned entries) |
HeaderRank | Rank header TextLabel | # (static, set by template) |
HeaderName | Name header TextLabel | Player name (static, set by template) |
HeaderValue | Value header TextLabel | VariableNameText setting (e.g. CASH) |
Each board Model also carries these attributes (managed by the plugin):
| Attribute | Type | Purpose |
|---|---|---|
ELS_LeaderboardId | string (GUID) | Stable unique link to the Settings entry. |
ELS_StatPath | string | Dot-path into PlayerData (e.g. Stats.Cash). |
Requirements
- Datastore & Purchase System must be installed and providing
PlayerDatafor each player. - API Services must be enabled (Game Settings → Security → Enable Studio Access to API Services) for OrderedDataStores to work in Studio.
Notifications System
PackId: NotificationsSystem · Version: 2.1.0 · Requires: nothing
What It Does
This is the pack that tells your players things. Three surfaces, five ready-made reasons to use them, and one line of code for anything else you want to announce.
- Toasts. Cards that slide in, stack, and leave on their own. Four shapes ship: title only, title and subtitle, image and title, and image with both.
- Chat lines. A system message in a colour you choose. Cheap, unmissable, and it never covers the screen.
- Banner. A large centred card for the things that matter: a restart, an event going live, a message from you. One line or two.
Installed on its own it already announces five things with no code at all: a player's friend joining with their headshot, someone buying with Robux (a line to the server plus a confirmation to the buyer), your rotating announcements, and anyone joining or leaving (off by default, since most places already do this their own way).
Every other RBLX Essentials pack lights up when this one is installed: a code redeemed, a pet arriving from something other than an egg, a free spin coming up, a session gift becoming claimable. None of them require it. They simply go quiet without it.
Plugin Configuration
Templates
Seven templates ship. Expanding one shows the card itself at its real proportions with its true pixel size underneath. Beside its name is the pencil, the same one General UI Kit uses for menus: click it and the template appears on screen and is selected in the Explorer, so you can drag it where you want and design it in Studio. Click it again when you are done. The plugin warns you if you leave one visible, because Visible is saved with your place.
Triggers
| Trigger | What it does | Ships |
|---|---|---|
FriendJoined | A toast, with their headshot, when a player's friend joins. | On |
Purchases | A server-wide chat line plus the buyer's own confirmation toast. Wants Datastore & Purchase System. | On |
Announcements | The rotating messages, on a timer. | On |
JoinLeave | A chat line for anyone joining or leaving. | Off |
Each row carries a ? you can hover for what that trigger does, and the section prints the folder path so you can find the file. A trigger's wording lives at the top of its own module, in a marked Config block. To change "Friend joined!", open FriendJoined and edit the line.
Announcements
| Field | What it does |
|---|---|
IntervalMinutes | Minutes between messages. Anything under half a minute is treated as half a minute. |
Surface | Chat or Banner. |
Shuffle | Pick at random instead of in order. It never repeats the message just shown. |
Message1..N | The messages themselves. Add and Delete are in the plugin; blank ones are skipped. |
Every message field understands three tokens: {player}, {item} and {price}. Anything else is left on screen exactly as you typed it, so a mistyped token is visible rather than silently blank. Turning the whole rotation off is the Announcements switch in Triggers, not a field here.
Uninstalling
That's everything from the plugin.
Below: how to restyle a template, write your own trigger, and announce your own things from code.
Designing the templates
The seven live in StarterGui > RBLXEssentials > NotificationsSystem > NotificationsSystemGUI > Templates and are yours to move, resize and restyle. Change BackgroundColor3 on a template frame to recolour the whole card.
They are drawn in General UI Kit's visual language, so they sit beside your menus rather than beside a different product. Their borders are UIStrokes on StrokeSizingMode = ScaledSize, which makes Thickness a fraction of the frame's height rather than a number of pixels: resize a card and its border keeps its proportions. That is how the whole kit is built.
Every Position and Size is in scale, never offset, with a UIAspectRatioConstraint holding each card's shape. A card is the same fraction of the screen on a phone as on a 4K monitor. Keep it that way when you move them: drag with the scale handles, not the offset ones.
Where they appear, and what queues behind what
Where you put a template is where its notifications appear. StackGroup decides who queues behind whom: give two templates the same group name and their notifications form one column, taking turns instead of landing on top of each other, whatever mix of heights they are. Leave a group empty and that template stacks on its own.
Out of the box the four toasts share the group Toasts, so a friend-joined card and a reward card queue in one column. The two banners share Banner. So a "friend joined" card can live in the bottom-left while a reward card comes down from the top-centre, and they never fight, as long as their groups differ.
Nothing ever waits for a slot. When a template is already showing its MaxOnScreen limit, the next one dismisses the oldest of that kind early and takes its place. A notification held back until a slot opened would arrive describing something that had already stopped being true.
Making your own template
Duplicate one in the Explorer and rename it. That is the whole procedure: the new name appears in the plugin immediately and can be used anywhere a template is named. It works because a template carries its own behaviour, as attributes on the frame:
| Attribute | What it does |
|---|---|
Duration | Seconds on screen. |
StackDirection | Down, Up, Left or Right. Leave it on Auto and the stack grows away from the nearest edge. |
MaxOnScreen | How many of this type show at once. At the limit a new one replaces the oldest. 0 means no limit. |
Sound | A sound from Sound System's SFX folder, picked from a dropdown of the ones you actually have. Silent, not broken, without that pack. |
Animation | Slide, Fade or Pop. |
StackGroup | Which column this template queues in. |
SlideFrom | Which edge the card slides in from. Only used when Animation is Slide, and it changes the entry only: the card still stops where you put it. |
The renderer fills three children by name: Title, Subtitle and Image. Image is a container, a round plate with a ring holding an ImageLabel called Content. The plate clips, so a picture that is not square is masked to the circle, and the plate is what shows while a headshot loads. Delete one and that field simply is not drawn, so your redesign cannot break the pack. Add one to a template that had none and it starts being filled.
The two that behave differently
The banners size themselves. Their width follows the longest line, from a floor of 420px up to 900px, past which the text truncates rather than running off the screen. A 40px margin sits either side of the words, and since the card hugs its text that margin is the only thing between the words and the border. It is the number to change if they look cramped. Their labels are the only ones in the pack with TextScaled off, because a self-sizing card needs the box to fit the text rather than the other way round.
Alert is the one that is not a card. A single line of red text at the bottom centre, no fill, no border, no shadow. It stacks upward, because it hangs off the bottom edge. Use it when something could not be done: not enough money, a full inventory, a pass they do not own. It leaves after three seconds rather than five, because a refusal is a correction rather than news, and it plays Code_Error instead of the reward sound.
Its navy outline is not decoration. With no card behind it, that UIStroke is the only thing separating red text from whatever colour your game happens to be. Keep it if you restyle.
Easy Pet System already speaks through it: try to open an egg without enough Cash, or equip a fourth pet, and the line you see is this template.
Everything else in the kit is TextScaled, which means the height of the label decides how big the text renders, not TextSize. Make a label taller to make its text bigger.
Announcing your own things
From any Script or LocalScript of your own:
local Notify = require(game:GetService("ReplicatedStorage")
.RBLXEssentials.Packs.NotificationsSystem.Notify)
-- server
Notify.Toast(player, "TitleSubtitle", { Title = "Well done", Subtitle = "You found the secret!" })
Notify.ToastAll("TitleOnly", { Title = "The boss has spawned!" })
Notify.Chat("Double XP is live for the next hour!", Color3.fromRGB(255, 215, 0))
Notify.ChatTo(player, "Welcome back!")
Notify.Banner(player, { Title = "Round starting", Subtitle = "Get to the arena" })
Notify.BannerAll({ Title = "Server restarting in 5 minutes" })
-- client: the same names, without the player
Notify.Toast("TitleOnly", { Title = "Saved!" })
The data table can carry Title, Subtitle, Image (an asset id), ImageUserId (resolved to that player's headshot) and Color.
It can also override the template's behaviour, for that one notification. Pass any of Duration, MaxOnScreen, Sound, Animation or SlideFrom and it wins over what the frame says:
Notify.Toast(player, "TitleOnly", { Title = "Saved!", Duration = 1, Animation = "Pop" })
The names are the template's own attribute names, so there is nothing extra to learn. A value the renderer cannot use is ignored and the template's own setting stands, so a typo costs that call its override rather than the message. Passing a Subtitle to a title-only card is harmless.
StackDirection is the one behaviour you cannot override per call, on purpose: direction belongs to a column rather than to a card. Set it on the template, where it describes the column.
Writing a trigger
Drop a ModuleScript into ServerScriptService > RBLXEssentials > Packs > NotificationsSystem > Triggers and it appears in the plugin with its own switch. Nothing else to wire:
local Players = game:GetService("Players")
local MyTrigger = {}
function MyTrigger.Start(ctx, trove)
trove:Connect(Players.PlayerAdded, function(player)
ctx.kernel.Notify:Toast(player, "TitleOnly", { Title = "Welcome!" })
end)
end
return MyTrigger
trovecleans up with the pack, so connect through it and you have nothing to disconnect.Startmust not yield. Do slow work in your owntask.spawn.- Give the module a
Descriptionattribute and that text becomes its ? tooltip in the plugin. - A module with no
Enabledattribute is on: you added it because you wanted it. The plugin writes the attribute the first time you flip the switch. - A module that fails to load, or errors while starting, is skipped with a warning. It cannot take the other triggers down with it.
For pack authors
A pack does not use the module above. It calls the kernel facade:
ctx.kernel.Notify:Toast(player, "TitleSubtitle", { Title = "…" }) -- server
ctx.kernel.Notify:Toast("TitleSubtitle", { Title = "…" }) -- client
ctx.kernel.Notify is a silent no-op until Notifications System registers itself as its backend. That is why no pack declares this one in manifest.deps and why none of them checks whether it is installed. The asymmetry is deliberate: a pack wants silence when the pack is absent, and a person writing code wants to be told, so the front-door module errors at the require line instead.
Hooking purchases yourself
Datastore & Purchase System publishes a BindableEvent that this pack listens to, and so can you:
local hook = game:GetService("ServerScriptService").RBLXEssentials
.Packs.DatastorePurchaseSystem:FindFirstChild("PurchaseCompleted")
if hook then
hook.Event:Connect(function(player, kind, id, currencySpent)
print(player.Name, "bought", kind, id, "for", currencySpent)
end)
end
kind is "product" or "gamepass", and it fires after the purchase is recorded as granted. It also fires for FreeMode grants, which is how you test a purchase announcement in Studio without spending Robux.
Menus
None. This is the only pack with no panel and no HUD button, because there is nothing to open.
Roulette System
PackId: RouletteSystem · Version: 2.1.0 · Requires: Datastore & Purchase System
What It Does
Roulette System adds a spin-to-win wheel. Players spend spins to land on rewards, earn a free spin on a cooldown, and can buy more with developer products. All of it is configured from the plugin.
- Weighted random rewards: Cash, Diamonds, more Spins, any number in the player's save (
Stat), or a module you write (Custom). A random pet from an Easy Pet System egg ships ready to use. - A free spin cooldown whose clock starts when a player reaches zero spins, and a spin lands when it runs out.
- +1 / +3 / +10 spin developer products, wired from the plugin.
- Seven wheel slots with a name, an icon, a type, an amount and a weight. Typing a name or icon in the plugin redraws the wheel in Studio as you type.
- If the client never reports the wheel finished, the server grants the reward anyway after 10 seconds, and on leave.
Plugin Configuration
Spins
| Setting | Default | What it does |
|---|---|---|
StartingSpins | 1 | Spins a brand new player starts with. |
MaxSpins | 0 | Cap on spins in hand. 0 means no cap. |
FreeSpinCooldown | 900 | Seconds until a free spin once the player runs out. |
Dev Products
Paste the developer product id each button should sell. 0 means nothing is sold and the button warns instead. The receipts are handled by Datastore & Purchase System, so a purchase is granted once and recorded like any other.
Rewards
The wheel at the top is the real one, redrawn as you edit. Each slot has five fields:
| Field | What it does |
|---|---|
Type | Cash, Diamonds, Spins, Stat (any dot path in Target, such as Stats.Gems) or Custom (a module named in Target). |
Target | Only for Stat and Custom. For Custom the plugin lists your reward modules as a dropdown. |
Amount | How much is granted. For Custom, how many times Grant runs. |
Weight | Relative chance. A slot at 100 lands twice as often as one at 50. 0 never lands. |
DisplayName | Text on the slot, applied to the GUI in Studio as you type and again at play time. |
Icon | Image on the slot (rbxassetid://…). Each slot ships with its own. |
Cash and Diamonds never go below zero. An unknown type, or a Stat or Custom slot with no target, is switched off at startup with a warning in the Output.
What ships in the seven slots
| Slot | Type | Amount | Weight |
|---|---|---|---|
| Reward1 | Cash | 100 | 100 |
| Reward2 | Cash | 250 | 80 |
| Reward3 | Cash | 500 | 50 |
| Reward4 | Cash | 1000 | 30 |
| Reward5 | Diamonds | 10 | 40 |
| Reward6 | Custom, RandomPet | 1 | 20 |
| Reward7 | Spins | 1 | 60 |
Uninstalling
Removes the pack from every service it was installed into. Your settings go with it.
That's everything from the plugin.
If you are not writing custom rewards or calling the pack from your own code, you can stop here.
Where things live
ReplicatedStorage.RBLXEssentials.Packs.RouletteSystem
├── manifest, Config, RewardLabels, Client, Actions/Apply Rewards
└── Settings/ Spins · Dev Products · Rewards/Reward1..7
ServerScriptService.RBLXEssentials.Packs.RouletteSystem
├── Roulette -- the rules
└── CustomRewards/RandomPet, GrantPet
StarterGui.RBLXEssentials.RouletteSystem.RouletteSystemGUI
With General UI Kit installed, its own Spin menu and HUD button win adoption and the wheel matches the rest of your UI. Without it the pack's own copy is used. They are the same design.
What it saves
The pack writes its own keys on join. There is nothing to add to Datastore's Default Data.
d.Roulette.Spins -- spins in hand
d.Roulette.LastSpinTime -- os.time() when they ran out (0 = no countdown)
Both are mirrored to player.PlayerData.Roulette so the client can read them.
Custom Rewards
Drop a ModuleScript next to GrantPet under ServerScriptService > RBLXEssentials > Packs > RouletteSystem > CustomRewards:
local MyReward = {}
function MyReward.Grant(player, d)
-- d is the live save inside a data update; changes persist
d.Stats = d.Stats or {}
d.Stats.Gems = (d.Stats.Gems or 0) + 1
end
return MyReward
Set the slot's Type to Custom and pick it from the Module dropdown. Amount is how many times Grant runs per spin. A slot pointing at a module that is not there is switched off at boot with a warning.
Two modules ship with the pack, both wanting Easy Pet System:
RandomPetis slot 6's default. It hatches one pet from an Easy Pet System egg using that egg's own odds, free, with the usual hatch toast.EGG_IDat the top picks the egg (Tier1Eggas shipped) andFALLBACKis what it grants when Easy Pet System is absent (25 Diamonds, ornilfor nothing).GrantPetgrants one specific pet, named inPET_ID.
API
Declare "RouletteSystem" in your pack's manifest.deps, then:
local roulette = ctx.require("RouletteSystem")
-- capped by MaxSpins; false if the amount is not positive
roulette.addSpins(player, 5)
-- 0 until the player's save has loaded
roulette.getSpins(player)
-- true, or false plus a reason you never show a player
local ok, why = roulette.canSpin(player)
Remotes
Created by the kernel under ReplicatedStorage > RBLXEssentials > Kernel > Remotes > RouletteSystem.
| Remote | Type | Description |
|---|---|---|
RequestSpin | RemoteFunction | Spends a spin and returns { Success = true, Reward = { Index, DisplayName, Icon } } or { Success = false, Error }. The reward is held until the wheel finishes. |
AnimationComplete | RemoteEvent | Grants the held reward. A second RequestSpin grants it too, as do the 10 second timeout and leaving. |
The free spin countdown is worked out on the client from Settings > Spins > FreeSpinCooldown, which replicates like any other setting. There is no separate config folder and no claim remote.
Sounds
Roulette_Click as the wheel passes each slot, Roulette_Reward when it lands. Both ship in Sound System's SFX group and play through the kernel, so they are silent rather than broken when that pack is not installed.
Time Rewards System
PackId: TimeRewardsSystem · Version: 2.1.0 · Requires: Datastore & Purchase System
What It Does
Two reward systems that run side by side: a seven day streak for coming back, and six timed gifts for staying. Types, amounts, icons and timers are all plugin settings.
- A seven day cycle on a cooldown you choose. Missing a day does not reset the cycle, it only pauses it.
- Six session gifts that unlock at fixed minutes of unbroken playtime. Session progress lives in memory and starts again when the player rejoins, because these are rewards for staying.
- Badges on both HUD buttons light up when something is claimable, and the daily panel opens by itself on join when a reward is waiting.
- The session icon counts down to the next gift on the HUD itself. It clears once a gift is waiting, because the badge says that better than a clock at 00:00.
- The current day announces itself: its tile grows, takes the gradient, and its icon bobs. The emphasis follows the cycle, so it moves on its own.
Plugin Configuration
Every reward slot, day or gift, shares the same four fields:
| Field | What it does |
|---|---|
Type | Cash, Diamonds, Spins, Stat or Custom. |
Target | Only for Stat (a data path such as Stats.Gems) or Custom, where the plugin lists your reward modules as a dropdown. |
Amount | How much is granted. For Custom, how many times your module runs. |
Icon | Image on the tile (rbxassetid://…). |
Tile labels are written from those fields: +500 for a currency, +1 SPIN for spins, +5 Gems for a stat. Custom is the exception, because there is no number to show and a module name is not for players, so the tile keeps whatever label you author on it. The shipped Day 7 reads "LEGENDARY PET".
Session Gifts
Gift slots add one field, IntervalMinutes: the minutes of unbroken session playtime before that gift unlocks.
| Gift | Interval | Type | Amount |
|---|---|---|---|
| 1 | 5 min | Cash | 10 |
| 2 | 10 min | Cash | 25 |
| 3 | 15 min | Cash | 50 |
| 4 | 30 min | Cash | 75 |
| 5 | 45 min | Cash | 100 |
| 6 | 60 min | Diamonds | 10 |
Daily Rewards
| Setting | Default | What it does |
|---|---|---|
DailyResetMode | Hours | Hours gives each player a private clock started by their own claim. Midnight unlocks everyone together at 00:00 UTC, so someone who claims at 23:50 can claim again ten minutes later. |
DailyRewardCooldownHours | 24 | Hours between claims. Ignored when the mode is Midnight. |
| Day | Type | Amount |
|---|---|---|
| 1 | Cash | 100 |
| 2 | Cash | 200 |
| 3 | Spins | 1 |
| 4 | Diamonds | 20 |
| 5 | Cash | 500 |
| 6 | Diamonds | 100 |
| 7 | Custom, GrantPet | 1 |
One day, open
An unknown Type, or a Stat or Custom slot with no Target, is switched off at startup with a warning in the Output, rather than failing the first time a player tries to claim it.
Uninstalling
Removes the pack from every service it was installed into. Your settings go with it.
That's everything from the plugin.
If you are not writing custom rewards or calling the pack from your own code, you can stop here.
What it saves
The daily half persists one key, created on join if it is not there:
data.TimeRewards = {
Daily = {
Streak = 1, -- position in the cycle (1..7), wraps after the last day
LastClaim = 0, -- os.time() of the last successful claim
}
}
Streak is a position in the cycle, not a count of consecutive days. It advances by one per claim and wraps. Someone who claims Day 3 and disappears for a month comes back to Day 4, because the cooldown is the only gate.
Session state (when they joined, which gifts they took) lives on the server for the length of the session and is never written to the datastore.
Custom Rewards
Drop a ModuleScript into ServerScriptService > RBLXEssentials > Packs > TimeRewardsSystem > CustomRewards:
local MyReward = {}
-- `d` is the live save inside a data update: changes persist and
-- replicate on their own. `amount` is the slot's Amount field.
function MyReward.Grant(player, d, amount)
d.Stats = d.Stats or {}
d.Stats.Gems = (d.Stats.Gems or 0) + amount
end
return MyReward
Set the slot's Type to Custom and pick it from the Module dropdown. A GrantPet example ships with the pack: it grants a pet of the tier named in PET_RARITY (Legendary as shipped) rather than one hardcoded pet, so the reward keeps matching its label after you edit your pet list. Set PET_ID instead to pin one exact pet.
The menus
The pack ships its own Rewards menu and HUD button so it works on its own. With General UI Kit installed, its copies win adoption and this pack drives those instead. The daily panel lives in General UI Kit only, because the current-day emphasis is applied from a template at runtime and has to follow the cycle rather than be frozen on one tile.
| Part | Purpose |
|---|---|
Container/Gifts/Gift<N>/ButtonFrame | Claim face: ClaimButton, TimeLeftText |
Container/Gifts/Gift<N>/RewardFrame | Reward face once claimed: Reward/ImageLabel, Reward/AmountText |
Container/ClaimedRewards | The "n of total claimed" line |
Container/Days/Day<N> | Day tile: DayLabel, Icon, Amount, ClaimButton/StatusText |
Container/Progress | Track, Fill (streak over days), ProgressText |
Container/Footer | DailyStreak, TimeLeft |
Reusing the day's motion anywhere
The bob on the current day is the same one General UI Kit gives any element, so you can put it on something of your own without code: tag it UI_Animated and set Animation = "SineBob". Day 7's sunburst spins the same way with Animation = "Rotate".
To retune the day icon itself, put any of IdleBobAmplitude (scale), IdleBobRotation (degrees) or IdleBobPeriod (seconds per cycle) as attributes on the panel's Container/Days frame.
API
local rewards = ctx.require("TimeRewardsSystem")
-- Where they are in the cycle, and how long until the next.
local daily = rewards.getDaily(player)
print(daily.dayIndex, daily.cooldownRemaining)
-- Second return: the new state, or why it was refused.
local ok, result = rewards.claimDaily(player)
-- The session half, by gift number.
rewards.getSession(player)
rewards.claim(player, 3)
A refusal is a reason, never a sentence, so the wording on screen stays yours: NotReady, AlreadyClaimed, OnCooldown, NoSuchGift, BadIndex, NoData.
Remotes
Created on demand under ReplicatedStorage > RBLXEssentials > Kernel > Remotes > TimeRewardsSystem.
| Remote | Type | Description |
|---|---|---|
ClaimGift | RemoteFunction | Claims a session gift by index. |
GetSession | RemoteFunction | The caller's session state, for the first paint and re-opens. |
ClaimDaily | RemoteFunction | Claims the current day. |
GetDaily | RemoteFunction | The caller's daily state, including the remaining cooldown. |
Every claim is validated on the server against the same rules the panel shows. The client is never trusted for eligibility.
Codes System
PackId: CodesSystem · Version: 2.1.0 · Requires: Datastore & Purchase System
What It Does
Codes System lets you hand out redeemable codes for a launch, a milestone, a video or a Discord drop, and lets players claim them from a menu. You write the codes in the plugin; each player can redeem each one once.
- Any number of codes, each granting
Cash,Diamonds, any other savedStat,Spinsfor the Roulette wheel, or aCustomreward you write yourself. - Case and spaces do not matter.
launch,LAUNCHandLaunchare the same code, so a player who pastes one with a trailing space still gets their reward. The box upper-cases as they type, so what they see is what will be matched. - An optional expiry per code, to the day.
- One redemption per player, remembered in their save.
- Guessing is throttled server-side, and a correct code clears the penalty immediately.
- Every word the panel can say is a setting you edit, so the pack speaks your game's language.
Codes are authored in the plugin, not fetched from anywhere. A new code means opening the place in Studio, adding it, and republishing. In exchange there is nothing to host, nothing to authorise, and no second place to look when something is wrong.
Plugin Configuration
The code list
One row per code: the code itself, what it grants, and a dot saying whether it is live, expired or refused. Add code and Delete code live here too, so the list grows and shrinks without touching the Explorer, and both are ordinary Ctrl+Z.
One code, open
| Field | What it does |
|---|---|
Code | What the player types. Write it however you want it read; matching ignores case and surrounding spaces. |
Type | Cash, Diamonds, Spins, Stat or Custom. |
Target | Only for Stat (a data path such as Stats.Gems) or Custom, where the plugin lists your reward modules as a dropdown. |
Amount | How much is granted. For Custom, how many times your module runs. |
Expires | YYYY-MM-DD in UTC, or empty for a code that never expires. The plugin gives you a calendar for it. |
Reward types
Spins only does anything with Roulette System installed, and Custom lists whatever modules you have dropped into the pack's CustomRewards folder. Neither is a declared dependency: a code whose pack is missing warns in the output instead of breaking the redemption.
Expiry
Expires is the day the code stops working, not its last day. 2026-09-01 means it dies at 00:00 UTC on the 1st, so 31 August is the last day anyone can redeem it. The plugin prints that last day under the field, so you never have to hold the rule in your head.
If you want a code to stop working on a date, set Expires rather than deleting it. The code stays visible in your settings and its history stays intact.
The codes that ship
| Slot | Code | Grants |
|---|---|---|
Code1 | LAUNCH | 1000 Cash |
Code2 | WELCOME | 50 Diamonds |
Code3 | FREESPINS | 5 roulette spins |
Code4 | LEGENDARY | a Legendary pet, through GrantPet |
They are examples. Change or delete them before you ship. None of them expires.
What the panel says
Settings/Messages holds every word a player can be told, one field per outcome.
| Field | Default |
|---|---|
Redeemed | Redeemed {code}! |
Empty | Type a code first! |
NoSuchCode | That code doesn't exist. |
AlreadyRedeemed | You've already used that code. |
Expired | That code has expired. |
CodeDisabled | That code isn't available. |
TooFast | Slow down a moment, then try again. |
NoData | Your data is still loading. Try again. |
GrantFailed | Something went wrong. Try again. |
Redeemed may contain {code}, which is replaced with the code that was redeemed. A field you clear falls back to its default rather than saying nothing, and so does a field that is missing entirely.
Uninstalling
Removes the pack from every service it was installed into. Your settings go with it, so export them first if you want them back.
That's everything from the plugin.
If you are not planning to write custom rewards or call the pack from your own code, you can stop here.
What it saves
One key, created on join if it is not there:
data.Codes = {
Redeemed = { LAUNCH = true, WELCOME = true },
}
A set of the codes this player has used, keyed in upper case. Renaming a code in Settings lets everyone redeem it again, which is correct: it is a different code.
Custom Rewards
Drop a ModuleScript into ServerScriptService > RBLXEssentials > Packs > CodesSystem > CustomRewards. It returns a table with a Grant function:
local MyReward = {}
-- `d` is the live player-data table inside a datastore update:
-- changes persist and replicate on their own. `amount` is the
-- code's Amount field.
function MyReward.Grant(player, d, amount)
d.Stats = d.Stats or {}
d.Stats.Gems = (d.Stats.Gems or 0) + amount
end
return MyReward
Set the code's Type to Custom and pick your module from the Module dropdown, which lists whatever is in that folder.
A GrantPet example ships with the pack. It grants a pet of the tier named in PET_RARITY (Legendary as shipped) rather than one hardcoded pet, so the reward keeps matching its label after you edit your pet list. Set PET_ID instead to pin one exact pet.
The menu
The pack ships its own Codes menu and HUD button so it works on its own. With General UI Kit installed, General UI Kit's copy wins and this pack drives that one instead: one menu, whichever pack provided it.
Inside the panel, the pack drives these by name:
| Part | Purpose |
|---|---|
Container/Code/TextBox | Where the player types. Upper-cased on every keystroke, with the caret left where it was. |
Container/Button | Redeem. |
Container/ErrorText | Refusals. |
Container/SuccessText | Confirmations. |
Rename any of these and the pack degrades rather than breaking: it warns in the output and, if only the two labels are missing, keeps redeeming silently. Everything else in the panel is yours to restyle.
API
Another pack reaches this one through the kernel, after declaring it in its own manifest.deps:
local codes = ctx.require("CodesSystem")
-- ok, reason | { Code, Type, Amount }
codes.redeem(player, "LAUNCH")
-- boolean; case does not matter here either
codes.hasRedeemed(player, "launch")
Refusals come back as machine reasons, never as sentences: Empty, NoSuchCode, AlreadyRedeemed, Expired, CodeDisabled, TooFast, NoData, GrantFailed. The caller decides the wording.
Remotes
Created on demand under ReplicatedStorage > RBLXEssentials > Kernel > Remotes > CodesSystem.
| Remote | Type | Description |
|---|---|---|
Redeem | RemoteFunction | Redeems the given text. Returns { Success = true, Code, Type, Amount } or { Success = false, Error }. |
Every redemption is validated server-side against the same rules the panel shows. The client is never trusted, and the throttle lives on the server rather than in the button.
Changelog
Version history for all packs and the plugin.
Version 2.1.0 Released 2026-09-15
RBLX Essentials Plugin 2.1.0 — Released 2026-09-15
- Choose installed UI templates from a floating dropdown. Original and Studded UI retain separate project edits when you switch between them.
- Menus and Buttons editors now follow the selected interface. Preview tiles scale with the miniature, and tooltips stay inside the plugin window.
- Import the free Studded UI visual kit as a General UI Kit template. A Get more UI templates link provides a selectable address for the template catalog.
- Studded UI remains visible as an installed template without opening a separate settings panel. Select and configure the active interface through General UI Kit.
- Edit interactive menu pages directly in Studio: switch between Collection, Eggs and Index from the menu editor without running gameplay scripts.
Sound System & Music Player 2.1.0 — Released 2026-09-15
- All six music-player templates now include an open button and a hide control; a seventh Studded template is included. Showing and hiding the player preserves music playback.
- Add optional PlayOneShot and PlayLoop client APIs with per-play volume and pitch, separate voice limits and cleanup when the owner is removed or SFX are disabled.
- Recover player controls when their GUI arrives late, preserve the selected template, and prevent duplicate initialization or stale UI connections.
- Expand the optional SFX library from 18 to 42 sounds: 24 additions selected from sounds used in Stone Skipping. No extra gameplay mappings are enabled automatically.
- The standalone Settings interface matches the current General UI Kit template.
- Four initially selected sounds are deferred because their current Roblox audio IDs cannot be distributed: WindLoop, UI_Confirm_01, Water_Splash_10 and Movement_Swish_04.
Datastore & Purchase System 2.1.0 — Released 2026-09-15
- Add SaveAndWait with confirmed storage results, frozen save snapshots, retry backoff and safer same-server rejoin handling.
- Add GUID session ownership to reject displaced writers, plus read-only ReadSaved and StoreName APIs.
- Add registerProfileHandler: profile rewards and purchase receipts are saved together before confirming a purchase. Duplicate receipts remain recognized without automatic history eviction.
- HasPassStrict checks stored entitlements as well as Roblox ownership independently of FreeMode. Legacy handlers remain available, but arbitrary external effects are not made transactional.
General UI Kit 2.1.0 — Released 2026-09-15
- Add Studded UI presentation support for the separately distributed free visual kit, including responsive typography, centered fixed-size stud textures and authored button behavior.
- Support optional gain-to-counter visual feedback when Notifications System 2.1.0 is installed.
- Show the configured demonstration Rebirth requirement and avoid rotation that breaks masking in affected panel animations. The requirement display does not add a server-side Rebirth economy.
- Studded roulette offers animate the button, icon and price together from the center without shifting adjacent offers. Green prices and a static white circular rim improve readability while spinning.
- The default Pets menu now includes Collection, Eggs and Index, with rarity-colored borders, consistent viewport framing and two-row equipment actions.
Easy Pet System 2.1.0 — Released 2026-09-15
- Add stored egg inventory, one-at-a-time opening through the native hatch reveal, and permanent species discovery for optional Collection, Eggs and Index tabs.
- Add GrantPet and GrantEggs APIs, profile helpers and a Changed signal, with complete-batch capacity checks and optional receipt-based delivery.
- Fusion consumes exactly the required number of copies. Stable inventory IDs and saved multipliers improve equipment ranking and prevent unnecessary preview rebuilding.
- Improve late GUI/data recovery, follower cleanup and frame-rate-independent smoothing. Keep disabled pass IDs disabled in FreeMode.
- Robux eggs use confirmed profile purchase handlers; ordinary cash-hatch requests cannot grant Robux eggs. Requires DataStore 2.1.0.
- The standalone Pets interface includes the revised default Collection, Eggs and Index layout. Stable grid sizing prevents a layout feedback loop when browsing tiers.
Roulette System 2.1.0 — Released 2026-09-15
- Paid spin bundles use DataStore profile purchase handlers. A bundle is granted in full or remains pending when there is insufficient capacity; requires DataStore 2.1.0.
- The included RandomPet reward uses the shared pet inventory and discovery helpers, retaining its existing diamond fallback when a pet cannot be granted.
Time Rewards System 2.1.0 — Released 2026-09-15
- Explicit custom-reward failure no longer consumes a session or daily claim. Optional preparation runs before profile updates, while existing callbacks that return nil remain compatible.
- The included pet reward validates the full batch and records species discoveries through Easy Pet System helpers.
- Support the revised daily reward presentation and claim states. Session rewards still reset on a normal new session; AFK reconnection persistence is not included in this update.
Codes System 2.1.0 — Released 2026-09-15
- Explicit custom-reward failure no longer consumes a code. Optional preparation resolves dependencies before profile updates, while existing nil-returning callbacks remain compatible.
- The included GrantPet reward validates the whole batch and records discoveries through Easy Pet System helpers instead of maintaining its own inventory schema.
Notifications System 2.1.0 — Released 2026-09-15
- Add CounterFlyout and a CounterEffects template for icons and amounts moving toward a GUI counter; optional tagged data bindings can trigger feedback on positive gains.
- Support independent entrance and exit directions, optional colors and native text fields in notification templates.
- Recover a late notification GUI with cancelable waits and clean up removed views. Counter flyouts are visual feedback, not delayed counter updates or a transaction ledger.
Compatibility and updating
- Update DataStore, Easy Pet System and Roulette together. Easy Pet System 2.1.0 and Roulette 2.1.0 require DataStore 2.1.0 for profile purchase handlers.
- The included pet reward examples in Codes, Time Rewards and Roulette require Easy Pet System 2.1.0 when used. Pets remain an optional companion for other rewards.
- Studded UI requires General UI Kit 2.1.0 and Plugin 2.1.0 for template selection and interaction. The free download alone is a visual kit.
- Back up customized pack folders before updating: variant switching preserves edits within a project, but pack installation still replaces pack folders and only restores compatible Settings values.
- Coordinate the DataStore rollout and restart old servers. Older writers do not honor the new session ownership protocol; do not change StoreVersion or wipe profiles to perform this update.
Unchanged: Kernel 2.1.0; EasyLeaderstatsSystem 2.0.0.