---
title: "Record and display player data"
url: /docs/en-us/tutorials/curriculums/core/scripting/record-and-display-player-data
last_updated: 2026-06-10T02:17:52Z
description: "Explains how to store and retrieve individual player data in an experience and display it visually through a leaderboard."
---

# Record and display player data

Now that you can detect when a player has collected a coin, this section of the tutorial teaches you how to count how many coins players have collected, and make that amount visible on a leaderboard.

## Create a module script to record coin collection

To handle the storage and management of each player's coin collection data, you need to create a `Class.ModuleScript` object to contain a data structure and functions that access coin collection data for every player. Module scripts are reusable code that other scripts can require. In this case, the `CoinService` requires this module script so that it can update coin collection data when players touch coins.

To create a module script:

1. In the **Explorer** window, hover over **ServerStorage** and click the **⊕** button. A contextual menu displays.
2. From the contextual menu, select **ModuleScript**. A new module script displays under **ServerStorage**. You are placing a module script into **ServerStorage** because you want to manage the coin collection logic on the server.![Studio's Explorer window with both the ServerScriptService's plus icon and ModuleScript object highlighted.](../../../../assets/tutorials/record-and-display-player-data/Insert-ModuleScript.png)
3. Rename the module script to **PlayerData**.![Studio's Explorer window with the PlayerData script highlighted under ServerStorage.](../../../../assets/tutorials/record-and-display-player-data/ModuleScript-Renamed-PlayerData.png)
4. Replace the default code with the following code:```lua
local PlayerData = {}
PlayerData.COIN_KEY_NAME = "Coins"

local playerData = {
  --[[
    [userId: string] = {
      ["Coins"] = coinAmount: number
    }
  ]]
}

local function defaultPlayerData()
  return {
    [PlayerData.COIN_KEY_NAME] = 0
  }
end

local function getData(player)
  local data = playerData[tostring(player.UserId)] or defaultPlayerData()
  playerData[tostring(player.UserId)] = data
  return data
end

function PlayerData.getValue(player, key)
  return getData(player)[key]
end

function PlayerData.updateValue(player, key, updateFunction)
  local data = getData(player)
  local oldValue = data[key]
  local newValue = updateFunction(oldValue)

  data[key] = newValue
  return newValue
end

return PlayerData
```**#### Code explanation** The module script defines a `PlayerData` table that contains zero or many `playerData` tables, which represent coin collection data for a player. Every script that requires this module script receives the same copy of the `PlayerData` table, allowing multiple scripts to modify and share coin collection data. #### Declare the data structures The module script begins with a declaration of an empty table, `PlayerData`, which is returned at the end of the script. It also contains accessor methods to get and set values in the table. The `playerData` table contains comments that describe the structure of the table, which makes the code easier to understand. In this case, a `playerData` table contains a `userId` and a corresponding field named `Coins` that represents the amount of collected coins for that player.```lua
local PlayerData = {}
PlayerData.COIN_KEY_NAME = "Coins"

local playerData = {
  --[[
    [userId: string] = {
        ["Coins"] = coinAmount: number
    }
  ]]
}

...

return PlayerData
```#### Define a local data accessor`getData()` is a local function that retrieves data for a specific `playerData` table. If a player hasn't collected a coin, it returns a table from the `defaultPlayerData()` function to ensure every player has some data associated with them. A common convention is to create simple, public-facing functions that offload logic to locally-scoped functions that do the heavy lifting.```lua
local function defaultPlayerData()
  return {
    [PlayerData.COIN_KEY_NAME] = 0
  }
end

local function getData(player)
  local data = playerData[tostring(player.UserId)] or defaultPlayerData()
  playerData[tostring(player.UserId)] = data
  return data
end
```#### Define public data accessors`getValue()` and `updateValue()` are public-facing functions that other scripts that require this module script can call. In our case, the **CoinService** uses these functions to update a player's coin collection data whenever that player touches a coin.```lua
function PlayerData.getValue(player, key)
 return getData(player)[key]
end

function PlayerData.updateValue(player, key, updateFunction)
  local data = getData(player)
  local oldValue = data[key]
  local newValue = updateFunction(oldValue)

  data[key] = newValue
  return newValue
end
```

## Implement a leaderboard

You can represent the coin collection data visually with an on-screen leaderboard. Roblox includes a built-in system that automatically generates a leaderboard using a default UI.

To create the leaderboard:

1. In the **Explorer** window, create a **ModuleScript** in **ServerStorage**, then rename the module script to **Leaderboard**.![Studio's Explorer window with the Leaderboard script highlighted under ServerStorage.](../../../../assets/tutorials/record-and-display-player-data/ModuleScript-Renamed-Leaderboard.png)
2. Replace the default code with the following code:```lua
local Leaderboard = {}

-- Creating a new leaderboard
local function setupLeaderboard(player)
  local leaderstats = Instance.new("Folder")
  -- 'leaderstats' is a reserved name Roblox recognizes for creating a leaderboard
  leaderstats.Name = "leaderstats"
  leaderstats.Parent = player
  return leaderstats
end

-- Creating a new leaderboard stat value
local function setupStat(leaderstats, statName)
  local stat = Instance.new("IntValue")
  stat.Name = statName
  stat.Value = 0
  stat.Parent = leaderstats
  return stat
end

-- Updating a player's stat value
function Leaderboard.setStat(player, statName, value)
  local leaderstats = player:FindFirstChild("leaderstats")
  if not leaderstats then
    leaderstats = setupLeaderboard(player)
  end

  local stat = leaderstats:FindFirstChild(statName)
  if not stat then
    stat = setupStat(leaderstats, statName)
  end

  stat.Value = value
end

return Leaderboard
```**#### Code explanation** The following sections describe how the leaderboard works in more detail. #### Create a leaderboard The `setupLeaderboard()` function creates a new folder instance named `leaderstats` and sets it as a child of the specified player. Roblox automatically recognizes a folder named `leaderstats` as a container of stats and creates a UI element to display the stats. It requires the values in `leaderstats` to be stored as "value" objects (such as `Class.StringValue`, `Class.IntValue` or `Class.NumberValue`).```lua
-- Creating a new leaderboard
local function setupLeaderboard(player)
  local leaderstats = Instance.new("Folder")
  -- 'leaderstats' is a reserved name Roblox recognizes for creating a leaderboard
  leaderstats.Name = "leaderstats"
  leaderstats.Parent = player
  return leaderstats
end

-- Creating a new leaderboard stat value
local function setupStat(leaderstats, statName)
  local stat = Instance.new("IntValue")
  stat.Name = statName
  stat.Value = 0
  stat.Parent = leaderstats
  return stat
end
```#### Update player stats`setStat()` is the only public function in the **Leaderboard** module. It creates stat values for a specified player or the leaderboard itself if it doesn't already exist.`Class.Instance:FindFirstChild()|FindFirstChild()` takes the name of an object and returns the object if it exists, or `nil` if it doesn't. It's a common, safe method of finding out if an object exists before you use it.```lua
-- Updating a player's stat value
function Leaderboard.setStat(player, statName, value)
  local leaderstats = player:FindFirstChild("leaderstats")
  if not leaderstats then
    leaderstats = setupLeaderboard(player)
  end

  local stat = leaderstats:FindFirstChild(statName)
  if not stat then
    stat = setupStat(leaderstats, statName)
  end

  stat.Value = value
end
```

## Integrate the module scripts

With both the **PlayerData** and **Leaderboard** module scripts complete, require them in the **CoinService** script to manage and display player coin data. To update **CoinService**:

1. In the **Explorer** window, open the **CoinService** script.
2. Replace the existing code with the following code:```lua
-- Initializing services and variables
local Workspace = game:GetService("Workspace")
local Players = game:GetService("Players")
local ServerStorage = game:GetService("ServerStorage")

-- Modules
local Leaderboard = require(ServerStorage.Leaderboard)
local PlayerData = require(ServerStorage.PlayerData)

local coinsFolder = Workspace.World.Coins
local coins = coinsFolder:GetChildren()

local COIN_KEY_NAME = PlayerData.COIN_KEY_NAME
local COOLDOWN = 10
local COIN_AMOUNT_TO_ADD = 1

local function updatePlayerCoins(player, updateFunction)
  -- Update the coin table
  local newCoinAmount = PlayerData.updateValue(player, COIN_KEY_NAME, updateFunction)

  -- Update the coin leaderboard
  Leaderboard.setStat(player, COIN_KEY_NAME, newCoinAmount)
end

-- Defining the event handler
local function onCoinTouched(otherPart, coin)
  if coin:GetAttribute("Enabled") then
    local character = otherPart.Parent
    local player = Players:GetPlayerFromCharacter(character)
    if player then
      -- Player touched a coin
      coin.Transparency = 1
      coin:SetAttribute("Enabled", false)
      updatePlayerCoins(player, function(oldCoinAmount)
        oldCoinAmount = oldCoinAmount or 0
        return oldCoinAmount + COIN_AMOUNT_TO_ADD
      end)

      task.wait(COOLDOWN)
      coin.Transparency = 0
      coin:SetAttribute("Enabled", true)
    end
  end
end

-- Setting up event listeners
for _, coin in coins do
  coin:SetAttribute("Enabled", true)
  coin.Touched:Connect(function(otherPart)
    onCoinTouched(otherPart, coin)
  end)
end
```**#### Code explanation** The changes to the original **CoinService** script include:
  - Importing the **PlayerData** and **Leaderboard** modules with the `Global.LuaGlobals.require()` function.
  - Declaring `COIN_AMOUNT_TO_ADD` as the number of coins to add when a player collects a coin, and `COIN_KEY_NAME` as the key name defined in **PlayerData**.
  - Creating the helper function `updatePlayerCoins()` to update the player's coin count and associated leaderboard stat.
  - Replacing the placeholder `print()` statement in `onCoinTouched()` with a call to `updatePlayerCoins()`.

## Playtest

It's time to see if the coin collection is working as intended. When you touch and collect a coin in the 3D world, you should be able to see the amount of coins you've collected on the leaderboard UI. To playtest your experience:

1. Choose **Test** from the dropdown menu and click the **Play** button to its right to begin the playtest.![Test option in the testing modes dropdown of Studio's mezzanine.](../../../../assets/studio/general/Mezzanine-Testing-Mode-Test.png)
2. Move your character to touch a coin. If your scripts are working correctly, the leaderboard UI displays and increase your coin count as you collect more coins.