Cleaner way to replicate a leaderboard GUI


I have a system implemented in my game where the default leaderboard is replaced with a custom GUI, where:

  • A player joins / leaves the game
  • A GUI stored in ReplicatedStorage is edited to append / remove a TextLabel with the player’s name
  • A remote event is then fired to ask clients to update the leaderboard (server → client)
  • The GUI is cloned by the client into the player’s PlayerGui

I’m wondering primarily whether this is the most optimised way to handle implementing my own, custom leaderboard, or if there might be a service (/ traditional way) to achieve the same goal.

Scripts can be found below:

I’ve tried to document my code clearly so that it’s easily readable by other programmers, please let me know if I can improve further.

Any feedback is appreciated!

Handle everything on the client. There is no need for a RemoteEvent.

You can use a UIListLayout to help position each TextLabel instead of calculating it manually.

1 Like

Until this comment, I had never realised that you could call game.Players.PlayerAdded on the client!

Thank you for what is, quite literally, a revelation!

(Thank you also for the tip on UIListLayout, I hadn’t thought of using that whilst designing!)

Edit: I’ve decided to stick with RemoteEvents, due to the inconsistency of connecting Players.PlayerAdded to a function in solo mode, as the player joins before the script executes.

You just need to loop through the all the players when they first join.

1 Like

Unfortunately, you can’t simply hook the PlayerAdded event to a function when playing in singleplayer, as the LocalScript runs after the player has joined, meaning that the player isn’t added to the leaderboard.

I did try something similar:

local function onPlayerAdded(plyr)


for k, v in pairs(game:GetService("Players"):GetChildren()) do

Unfortunately, though, this causes things to iterate twice, which isn’t particularly helpful when working with a singular GUI.

Not sure what you mean. It only prints once. Regular LocalScript in StarterPlayerScripts.

local Players = game:GetService("Players")

local function playerAdded(player: Player)


for _, player in Players:GetPlayers() do

That’s really odd.

Before, print debugging output two of my name to the terminal, now it’s only one. Not a clue why!

Either way, thanks for your help, I’ve been able to condense everything to one LocalScript, which is a whole lot neater.