---
title: "Record and display player data"
url: /docs/en-us/tutorials/curriculums/core/scripting/record-and-display-player-data
last_updated: 2026-10-20T22:18:12Z
description: "Explains how to store and retrieve individual player data in a game and display it visually through a leaderboard."
---

# Record and display player data

Now that you can detect when a player has collected a coin, you need 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.

#### Build with Assistant

```text
In ServerStorage, create a ModuleScript named PlayerData 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
\`\`\`
```

#### Build it Yourself

1. In the **Explorer**, hover over **ServerStorage**, click **⊕**, and insert a **ModuleScript**. Rename it **PlayerData**. You should place the module script into **ServerStorage** to manage the coin collection logic on the server.
2. Replace the default code with the following code to define the table that maps each player's ID to their data:

```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.

#### Build with Assistant

```text
In ServerStorage, create a ModuleScript named Leaderboard 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
\`\`\`
```

#### Build it Yourself

1. In the **Explorer**, insert another **ModuleScript** into **ServerStorage** and rename it `Leaderboard`. This one sits next to `PlayerData` so `CoinService` can `require` both from the same place.
2. Replace the default code with the following code to create the folder and the value object on the first call, and then update the value on every call after:

```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 modules in place, require them in the `CoinService` script to manage and display player coin data.

#### Build with Assistant

```text
In ServerScriptService, replace the existing CoinService Script's code with the following:

\`\`\`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
\`\`\`
```

#### Build it Yourself

1. In the **Explorer**, open the existing `CoinService` script inside **ServerScriptService**.
2. Replace the code with the following code to update `PlayerData` and the leaderboard in a single step:

```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

Select **Test** from the dropdown and click **Play**. Walk into a coin and confirm that the leaderboard appears in the corner and that your coin count increases as you collect more coins.