Skip to content

Server Functions

Server-side PlayerState API for data management and persistence.

Server Only - Security

Server scripts only. No built-in RemoteEvents - create your own with validation.
Never trust client data!

Initialization Functions

Init()

Init(player, existingData?)

Initializes PlayerState for a player. Must be called when a player joins.

Parameters:

  • player: Player - The player to initialize
  • existingData?: PlayerData - Optional existing data (not currently supported)

Returns: boolean - Success status

Example
lua
local success = PlayerState.Init(player)
if success then
    print("Player data loaded successfully")
else
    warn("Failed to load player data")
    -- Player will be kicked automatically
end

Notes:

  • Automatically loads data from ProfileStore and creates Replica for client sync
  • Handles ProfileStore session management with automatic leaderstats integration
  • Enhanced data validation and cleanup
  • Player will be kicked if data fails to load

Basic Data Functions

Set()

Set(player, key, value) → boolean

Sets a top-level data value with enhanced validation.

Parameters: player: Player, key: string, value: any
Returns: boolean - Success status (false if leaderstats modification attempted)

Example
lua
local success = PlayerState.Set(player, "Coins", 1000)
if success then
    print("Coins updated successfully")
else
    warn("Failed to update coins")
end

Note

Use SetPath() for nested paths instead of dot notation


Get()

Get(player, key) → any

Gets a top-level data value with validation. Automatically clones tables when AutoCloneTables is enabled in configuration.

Parameters: player: Player, key: string
Returns: any - The value, or nil if not found. Tables are cloned if AutoCloneTables is enabled.

Example
lua
local coins = PlayerState.Get(player, "Coins")
local level = PlayerState.Get(player, "Level")
print(`Player {player.Name} has {coins or 0} coins and is level {level or 1}`)

-- With AutoCloneTables enabled, tables are automatically cloned
local inventory = PlayerState.Get(player, "Inventory") -- Safe to modify
table.insert(inventory, newItem) -- Won't affect original if AutoCloneTables is true

Note

  • When AutoCloneTables is enabled in config, table values are automatically deep cloned
  • Prevents accidental modification of internal data structures
  • Configure in PlayerStateConfig.Server.AutoCloneTables

Path-based Functions

SetPath()

SetPath(player, path, value) → boolean

Sets a nested data value using dot notation with enhanced validation.

Parameters: player: Player, path: ValuePath, value: any
Returns: boolean - Success status

Example
lua
local success = PlayerState.SetPath(player, "Plot.Likes", 50)
if success then
    print("Plot likes updated")
end

PlayerState.SetPath(player, "Settings.MusicEnabled", false)
PlayerState.SetPath(player, "Stats.HighScore", 1200)

Note

Returns success status with optimized path parsing and caching


GetPath()

GetPath(player, path) → any

Gets a nested data value using dot notation with optimized performance. Automatically clones tables when AutoCloneTables is enabled in configuration.

Parameters: player: Player, path: ValuePath
Returns: any - The value, or nil if not found. Tables are cloned if AutoCloneTables is enabled.

Example
lua
local likes = PlayerState.GetPath(player, "Plot.Likes")
local musicEnabled = PlayerState.GetPath(player, "Settings.MusicEnabled")
local highScore = PlayerState.GetPath(player, "Stats.HighScore")

-- With AutoCloneTables enabled, tables are automatically cloned
local settings = PlayerState.GetPath(player, "Settings") -- Safe to modify
settings.NewOption = true -- Won't affect original if AutoCloneTables is true

Note

  • Optimized nested value retrieval with enhanced path caching system
  • When AutoCloneTables is enabled in config, table values are automatically deep cloned
  • Prevents accidental modification of internal data structures
  • Configure in PlayerStateConfig.Server.AutoCloneTables

Numeric Operation Functions

Increment()

Increment(player, key, amount?) → boolean

Increments a numeric value with validation. Works with both top-level keys and nested paths.

Parameters: player: Player, key: string, amount: number? (defaults to 1)
Returns: boolean - Success status

Example
lua
-- Increment top-level values
local success = PlayerState.Increment(player, "Coins", 100)
if success then
    print("Coins increased by 100")
end

-- Increment by 1 (default)
PlayerState.Increment(player, "Level")

-- Increment nested values using paths
PlayerState.Increment(player, "Plot.Likes", 5)
PlayerState.Increment(player, "Stats.HighScore", 250)
PlayerState.Increment(player, "Stats.GamesPlayed") -- defaults to +1

Note

Only works with numeric values - warns if value is not a number

Numeric Values Only

Increment() only works with numeric values. Attempting to increment a non-numeric value will return false and show a warning.


Decrement()

Decrement(player, key, amount?) → boolean

Decrements a numeric value with validation. Works with both top-level keys and nested paths.

Parameters: player: Player, key: string, amount: number? (defaults to 1)
Returns: boolean - Success status

Example
lua
-- Spend coins (top-level)
local success = PlayerState.Decrement(player, "Coins", 50)
if success then
    print("Coins decreased by 50")
else
    warn("Failed to spend coins")
end

-- Decrement by 1 (default)
PlayerState.Decrement(player, "Lives")

-- Decrement nested values using paths
PlayerState.Decrement(player, "Plot.Health", 10)
PlayerState.Decrement(player, "Stats.Attempts", 1)

Note

Internally uses Increment() with negative amounts

Performance & Safety

Use Increment() and Decrement() for numeric operations instead of Get() + Set() patterns. They're safer and more efficient!

lua
-- ❌ Less efficient and less safe
local coins = PlayerState.Get(player, "Coins")
PlayerState.Set(player, "Coins", coins + 100)

-- ✅ Better approach
PlayerState.Increment(player, "Coins", 100)

Dictionary Operations: SetInDict() and RemoveFromDict() now use efficient nested path updates to prevent race conditions and minimize network traffic.

Batch Operation Functions

SetValues()

SetValues(player, values) → boolean

Sets multiple top-level values at once with leaderstats protection.

Parameters: player: Player, values: {[string]: any}
Returns: boolean - Success status

Example
lua
local success = PlayerState.SetValues(player, {
    Coins = 1000,
    Level = 5,
    Experience = 750
})

if success then
    print("Batch update successful")
end

Note

Returns success status and prevents leaderstats modification in batch

Performance

Use SetValues() instead of multiple Set() calls for better performance!


BatchSetValues()

BatchSetValues(player, operations) → boolean

Queues multiple path-based operations for optimized batch processing.

Parameters: player: Player, operations: {BatchOperation}
Returns: boolean - Success status

lua
local success = PlayerState.BatchSetValues(player, {
    {path = "Coins", value = 1000},
    {path = "Level", value = 5},
    {path = "Plot.Likes", value = 50}
})
lua
local operations = {
    {path = "Coins", value = 2000},
    {path = "Experience", value = 500},
    {path = "Plot.Likes", value = 100},
    {path = "Settings.MusicEnabled", value = true},
    {path = "Stats.GamesPlayed", value = 25}
}

PlayerState.BatchSetValues(player, operations)

Notes:

  • High-performance batch processing system with automatic optimization
  • Configurable batch delay and size limits with auto-flush
  • Prevents leaderstats modification

Batch Performance

  • Operations are grouped by path structure for optimal performance
  • Root-level operations are batched together
  • Nested operations are intelligently grouped
  • Auto-flush occurs at 20 operations or 0.03s delay

BatchUpdateValues()

BatchUpdateValues(player, operations) → boolean

Applies multiple increment/decrement operations at path-based locations in a single batch. Validates that each target value is numeric, then commits all updates via BatchSetValues().

Parameters: player: Player, operations: {BatchUpdateOperation}
Returns: boolean - Success status (false if validation fails or any path is non-numeric)

Each BatchUpdateOperation has:

  • path: string - Dot-notation path to a numeric value
  • operation: "Increment" | "Decrement"
  • amount: number? - Optional; defaults to 1
Example
lua
local success = PlayerState.BatchUpdateValues(player, {
    {path = "Coins", operation = "Increment", amount = 100},
    {path = "Plot.Likes", operation = "Increment", amount = 5},
    {path = "Lives", operation = "Decrement"},  -- amount defaults to 1
    {path = "Stats.GamesPlayed", operation = "Increment"}
})

if success then
    print("Batch update applied")
else
    warn("BatchUpdateValues failed (invalid player, missing replica, or non-numeric path)")
end

Numeric paths only

If any path points to a non-numeric value, the function warns and returns false; no changes are applied.


FlushBatch()

FlushBatch(player) → boolean

Immediately processes all queued batch operations for a player.

Parameters: player: Player
Returns: boolean - Success status

Example
lua
-- Queue operations
PlayerState.BatchSetValues(player, {
    {path = "Coins", value = 1000},
    {path = "Level", value = 5}
})

-- Force immediate processing
PlayerState.FlushBatch(player)

Note

Manual batch processing control for critical operations

Array Operation Functions

AddToArray()

AddToArray(player, arrayPath, item) → boolean

Adds an item to the end of an array with validation.

Parameters: player: Player, arrayPath: ArrayPath, item: any
Returns: boolean - Success status

Example
lua
-- Add item to inventory
local success = PlayerState.AddToArray(player, "Inventory", {
    Id = "sword_001",
    Name = "Iron Sword",
    Rarity = "Common"
})

if success then
    print("Item added to inventory")
end

-- Add achievement
PlayerState.AddToArray(player, "Achievements", "first_kill")

Note

Enhanced array operation with success return and validation

Array Indexing

Arrays are 1-indexed in Lua. Index 1 is the first item!


RemoveFromArray()

RemoveFromArray(player, arrayPath, index) → boolean

Removes an item from an array by index with bounds checking.

Parameters: player: Player, arrayPath: ArrayPath, index: number (1-based)
Returns: boolean - Success status

Example
lua
-- Remove first item from inventory
local success = PlayerState.RemoveFromArray(player, "Inventory", 1)
if not success then
    warn("Failed to remove item")
end

-- Remove specific achievement
PlayerState.RemoveFromArray(player, "Achievements", 3)

Note

Safe index-based removal with bounds checking and validation


UpdateArrayItem()

UpdateArrayItem(player, arrayPath, index, newItem) → boolean

Updates an item in an array by index with validation.

Parameters: player: Player, arrayPath: ArrayPath, index: number (1-based), newItem: any
Returns: boolean - Success status

Example
lua
-- Update inventory item
local success = PlayerState.UpdateArrayItem(player, "Inventory", 1, {
    Id = "sword_002",
    Name = "Steel Sword",
    Rarity = "Rare",
    Enchantments = {"Sharpness", "Durability"}
})

if success then
    print("Item upgraded successfully")
end

Note

Safe array item modification with index validation and bounds checking

Dictionary Operation Functions

SetInDict()

SetInDict(player, dictPath, key, value) → boolean

Sets a value in a dictionary/table with efficient nested path updates and automatic dictionary creation.

Parameters: player: Player, dictPath: DictPath, key: string, value: any
Returns: boolean - Success status

Example
lua
-- Set building data (creates dictionary if needed)
local success = PlayerState.SetInDict(player, "Plot.Buildings", "House", {
    Level = 2,
    Material = "Wood",
    Position = Vector3.new(10, 0, 10)
})

-- Set game setting
PlayerState.SetInDict(player, "Settings", "GraphicsQuality", "High")

-- Set player stat
PlayerState.SetInDict(player, "Stats", "BestTime", 120.5)

Note

Efficient nested path updates with automatic dictionary creation and race condition prevention


GetFromDict()

GetFromDict(player, dictPath, key) → any

Gets a value from a dictionary using a key with automatic type conversion. Automatically clones tables when AutoCloneTables is enabled in configuration.

Parameters: player: Player, dictPath: DictPath, key: string | number
Returns: any - The value, or nil if not found. Tables are cloned if AutoCloneTables is enabled.

Example
lua
-- Get building data
local houseData = PlayerState.GetFromDict(player, "Plot.Buildings", "House")
if houseData then
    print("House level:", houseData.Level)
    print("House material:", houseData.Material)
end

-- Get with numeric key (automatically converted to string)
local item = PlayerState.GetFromDict(player, "Inventory.Equipped", 1)

-- Get setting
local quality = PlayerState.GetFromDict(player, "Settings", "GraphicsQuality")
print("Graphics quality:", quality or "Default")

Note

  • Automatically converts number keys to strings for consistency
  • When AutoCloneTables is enabled in config, table values are automatically deep cloned
  • Prevents accidental modification of internal data structures

RemoveFromDict()

RemoveFromDict(player, dictPath, key) → boolean

Removes a key from a dictionary using efficient nested path updates.

Parameters: player: Player, dictPath: DictPath, key: string
Returns: boolean - Success status

Example
lua
-- Remove building
local success = PlayerState.RemoveFromDict(player, "Plot.Buildings", "OldHouse")
if success then
    print("Building removed")
end

-- Remove obsolete setting
PlayerState.RemoveFromDict(player, "Settings", "ObsoleteOption")

Note

Efficient nested path removal with race condition prevention and minimal network traffic

Cache Management Functions

ClearPathCache()

ClearPathCache() → void

Clears the global path parsing cache for memory management.

Parameters: None
Returns: Nothing

Example
lua
-- Clear path cache during server maintenance
PlayerState.ClearPathCache()

-- Useful for debugging path-related issues

Note

Global cache management for path parsing with automatic size management (max 1000 entries)

Events

Data Manipulation Guidelines

BeforeSave & ProfileLoaded: Use PlayerState functions for data changes (recommended) as they provide validation, leaderstats sync, and proper error handling. Direct data manipulation via the data parameter bypasses these safeguards.

ProfileUnloaded: PlayerState functions are disabled. Only use direct data access via the data parameter for read-only operations or external system synchronization.

BeforeSave()

BeforeSave:Connect(listener)

Fired before player data is saved, allowing for final data modifications.

Parameters: listener: (player: Player, data: PlayerData) -> ()
Returns: Connection - Event connection for disconnecting

Example
lua
PlayerState.BeforeSave:Connect(function(player, data)
    -- Use PlayerState functions for data changes (recommended)
    PlayerState.Set(player, "LastSaveTime", os.time())
    PlayerState.SetPath(player, "Stats.TotalPlayTime", data.Stats.PlayTime or 0)

    -- Direct data manipulation bypasses validation (use sparingly)
    data.FinalCoins = data.Coins -- Direct access for read-only operations

    print(`Saving data for {player.Name}`)
end)

Note

Fired before automatic saves with PlayerState functions working normally. Use PlayerState functions for data changes (recommended) as they provide validation and leaderstats sync.


ProfileLoaded()

ProfileLoaded:Connect(listener)

Fired when a player's profile data has been loaded and is ready for use.

Parameters: listener: (player: Player, data: PlayerData) -> ()
Returns: Connection - Event connection for disconnecting

Example
lua
PlayerState.ProfileLoaded:Connect(function(player, data)
    -- Use PlayerState functions for data changes (recommended)
    PlayerState.Set(player, "JoinTime", os.time())
    PlayerState.SetPath(player, "Stats.Logins", (data.Stats.Logins or 0) + 1)

    -- Read data for initialization logic
    print(`{player.Name}'s data loaded with {data.Coins} coins`)

    -- Setup player-specific features
    setupPlayerUI(player)
end)

Note

Fired after PlayerState.Init() completes successfully. PlayerState functions work normally - use them for data changes (recommended) for validation and proper sync.


ProfileUnloaded()

ProfileUnloaded:Connect(listener)

Fired when a player's profile session ends and data is being unloaded.

Parameters: listener: (player: Player, data: PlayerData) -> ()
Returns: Connection - Event connection for disconnecting

Example
lua
PlayerState.ProfileUnloaded:Connect(function(player, data)
    -- Cleanup player-specific systems
    print(`{player.Name}'s profile unloaded`)

    -- Direct data access only - PlayerState functions don't work here
    local finalCoins = data.Coins
    local playTime = data.Stats.PlayTime or 0

    -- Save to external systems if needed
    saveToAnalytics(player.UserId, finalCoins, playTime)
end)

Note

Fired when profile session ends. PlayerState functions do NOT work during this event - only direct data access via the data parameter is available for read-only operations.

Utility Functions

GetAll()

GetAll(player) → PlayerData?

Gets all player data with validation.

Parameters: player: Player
Returns: PlayerData? - Complete data table, or nil if not found

Example
lua
local allData = PlayerState.GetAll(player)
if allData then
    print("Player has", allData.Coins, "coins")
    -- Safely iterate through data
    for key, value in pairs(allData) do
        print(`{key}: {value}`)
    end
end

Note

Returns reference to actual data (be careful with modifications)


GetReplica()

GetReplica(player) → ReplicaInstance?

Gets the raw Replica instance for advanced operations.

Parameters: player: Player
Returns: ReplicaInstance? - Replica instance, or nil if not found

Example
lua
local replica = PlayerState.GetReplica(player)
if replica then
    -- Advanced Replica operations
    replica:OnChange(function(action, path, ...)
        print(`Data changed: {action} at {table.concat(path, ".")}`)
    end)

    -- Check replica status
    print("Replica is active:", replica:IsActive())
end

Note

Enhanced validation ensures replica is active


GetProfile()

GetProfile(player) → ProfileStoreProfile?

Gets the raw ProfileStore profile for advanced operations.

Parameters: player: Player
Returns: ProfileStoreProfile? - Profile instance, or nil if not found

Example
lua
local profile = PlayerState.GetProfile(player)
if profile then
    -- Advanced ProfileStore operations
    print("Profile is active:", profile:IsActive())

    -- Access profile metadata
    print("Profile tags:", profile.Tags)
end

Note

Enhanced validation ensures profile is active


Clone()

Clone(value, deep?) → any

Clones a value, with optional deep cloning for tables. Useful for safely copying data without modifying the original.

Parameters: value: any, deep: boolean? (defaults to false)
Returns: any - Cloned value (tables are cloned, other types returned as-is)

Example
lua
-- Shallow clone (default)
local originalTable = {coins = 100, items = {sword = true}}
local clonedTable = PlayerState.Clone(originalTable)
clonedTable.coins = 200 -- Only modifies clone
clonedTable.items.sword = false -- Modifies original too (shallow clone)

-- Deep clone (nested tables are also cloned)
local deepCloned = PlayerState.Clone(originalTable, true)
deepCloned.coins = 300 -- Only modifies clone
deepCloned.items.sword = false -- Only modifies clone (deep clone)

-- Clone non-table values (returns as-is)
local number = PlayerState.Clone(100) -- Returns 100
local string = PlayerState.Clone("test") -- Returns "test"

Note

  • Non-table values are returned unchanged
  • Shallow clone (default) uses table.clone() for performance
  • Deep clone recursively clones all nested tables
  • Use deep clone when you need to modify nested structures safely

GetOfflineData()

GetOfflineData(userId) → PlayerData?

Retrieves player data for offline users from ProfileStore. Automatically checks if the player is currently in the server first for better performance.

Parameters: userId: number
Returns: PlayerData? - Player data, or nil if not found or invalid

Example
lua
local offlineData = PlayerState.GetOfflineData(123456789)
if offlineData then
    print("Offline player has", offlineData.Coins, "coins")
    print("Level:", offlineData.Level)
else
    print("No offline data found for user")
end

Note

  • Automatically checks if player is in server first (uses live data if available)
  • Falls back to ProfileStore for offline players
  • More efficient than previous implementation

SetOfflineData()

SetOfflineData(userId, path, value) → boolean

Sets data for offline users in ProfileStore. Automatically checks if the player is currently in the server first and uses live data operations when available.

Parameters: userId: number, path: string, value: any
Returns: boolean - Success status

Example
lua
-- Set top-level data
local success = PlayerState.SetOfflineData(123456789, "Coins", 5000)
if success then
    print("Offline coins updated successfully")
end

-- Set nested data using paths
PlayerState.SetOfflineData(123456789, "Plot.Likes", 100)
PlayerState.SetOfflineData(123456789, "Settings.MusicEnabled", false)

Note

  • Automatically checks if player is in server first (uses live PlayerState functions if available)
  • Falls back to ProfileStore messaging for offline players with active sessions
  • Creates new session if player is completely offline
  • More efficient handling of online vs offline players

SaveData()

SaveData(player) → boolean

Forces data save validation check.

Parameters: player: Player
Returns: boolean - Profile active status

Example
lua
-- Check if save will succeed before critical operation
local canSave = PlayerState.SaveData(player)
if canSave then
    -- Proceed with critical operation
    PlayerState.Set(player, "ImportantData", value)
else
    warn("Cannot save data - profile inactive")
end

Note

Returns profile active status for validation


WipePlayerData()

WipePlayerData(player) → boolean

Wipes all player data and resets it to the template values.

Parameters: player: Player
Returns: boolean - Success status

Example
lua
-- Reset player data to template values
local success = PlayerState.WipePlayerData(player)
if success then
    print(`{player.Name}'s data has been reset`)
else
    warn(`Failed to reset {player.Name}'s data`)
end

Note

Resets all data to CONFIG.Profile.Template values and kicks player


WipeOfflinePlayerData()

WipeOfflinePlayerData(userId) → boolean

Wipes data for offline players and resets it to template values.

Parameters: userId: number
Returns: boolean - Success status

Example
lua
-- Reset offline player data
local success = PlayerState.WipeOfflinePlayerData(123456789)
if success then
    print("Offline player data reset successfully")
else
    warn("Failed to reset offline player data")
end

Note

Works with offline players not currently in the game

🏆 Global Leaderboard

Configuration Required & Runtime Validation

Leaderboard functionality requires proper configuration in PlayerStateConfig!

Make sure your PlayerStateConfig.Server.Leaderboard section is properly configured:

lua
Server = {
    Leaderboard = {
        Enabled = true,
        DataStoreName = "PlayerState_Leaderboards",
        TrackedStats = {"Coins", "Level", "HighScore"},
        SyncInterval = 60,
        UpdateOnPlayerLeave = true,
    },
}

Runtime Validation: Functions now validate configuration at runtime and will warn/return appropriate values if misconfigured:

  • GetLeaderboard() returns empty array {}
  • GetPlayerRank() returns nil
  • UpdateLeaderboard() returns false

Client-synced rank/score (for UI)

When leaderboards are enabled, PlayerState syncs each player’s current leaderboard display info to the client via a replicated _LeaderboardRanks table.

What this gives you:

  • Reliable UI data: the client can read {rank, score} for tracked stats without re-deriving it.
  • Instant-feeling score: when a tracked stat changes (via Set, SetPath, SetValues, etc.), the synced score updates right away.
  • Rank on refresh: rank updates on the normal server refresh cycle (based on SyncInterval).

Important:

  • _LeaderboardRanks is temporary display data and is treated as non-persistent (it will not be saved into player data).
  • On the client, use PlayerStateClient.GetLeaderboardInfo(statName) to read this value.

GetLeaderboard()

GetLeaderboard(statName, topCount) → {LeaderboardEntry}

Retrieves the top players for a specific leaderboard statistic.

Parameters: statName: string, topCount: number
Returns: {LeaderboardEntry} - Array of leaderboard entries sorted by score (highest first), or empty array if configuration issues

Example
lua
-- Get top 10 coin leaders
local coinLeaderboard = PlayerState.GetLeaderboard("Coins", 10)
for i, entry in ipairs(coinLeaderboard) do
    print(`#{i}: User {entry.userId} - {entry.score} coins`)
end

-- Get top 100 high scores
local highScores = PlayerState.GetLeaderboard("HighScore", 100)
if #highScores > 0 then
    print(`Highest score: {highScores[1].score} by User {highScores[1].userId}`)
end

Note

Results are cached for 15 seconds to improve performance.

Configuration Validation

This function validates configuration at runtime:

  • Returns empty array {} and warns if CONFIG.Leaderboard.Enabled = false
  • Returns empty array {} and warns if the statName is not in CONFIG.Leaderboard.TrackedStats
  • Requires the stat name to be properly configured before calling

GetPlayerRank()

GetPlayerRank(player, statName) → number?

Gets a player's current rank on a specific leaderboard.

Parameters: player: Player, statName: string
Returns: number? - Player's rank (1-based), or nil if not ranked, invalid player, or configuration issues

Example
lua
-- Get player's coin rank
local coinRank = PlayerState.GetPlayerRank(player, "Coins")
if coinRank then
    print(`You're ranked #{coinRank} for coins!`)
else
    print("You're not yet ranked for coins")
end

-- Check multiple ranks
local levelRank = PlayerState.GetPlayerRank(player, "Level")
local scoreRank = PlayerState.GetPlayerRank(player, "HighScore")

if levelRank and scoreRank then
    print(`Level Rank: #{levelRank}, Score Rank: #{scoreRank}`)
end

Note

Returns nil if player has no score for this stat or if the player is invalid.

Configuration Validation

This function validates configuration at runtime:

  • Returns nil and warns if CONFIG.Leaderboard.Enabled = false
  • Returns nil and warns if the statName is not in CONFIG.Leaderboard.TrackedStats
  • Returns nil if the player is invalid or not found
  • Requires the stat name to be properly configured before calling

UpdateLeaderboard()

UpdateLeaderboard(player, statName, score) → boolean

Manually updates a player's leaderboard entry for a specific statistic.

Parameters: player: Player, statName: string, score: number
Returns: boolean - Success status, or false if configuration or validation issues

Example
lua
-- Update player's coin leaderboard
local success = PlayerState.UpdateLeaderboard(player, "Coins", 5000)
if success then
    print("Leaderboard updated successfully")
else
    warn("Failed to update leaderboard")
end

-- Update multiple stats
PlayerState.UpdateLeaderboard(player, "Level", 25)
PlayerState.UpdateLeaderboard(player, "HighScore", 1200)

Note

This function immediately updates the leaderboard. Most stats are automatically updated when data changes, but you can use this for manual updates.

Configuration Validation

This function validates configuration at runtime:

  • Returns false and warns if CONFIG.Leaderboard.Enabled = false
  • Returns false and warns if the statName is not in CONFIG.Leaderboard.TrackedStats
  • Returns false if the player is invalid
  • Requires the stat name to be properly configured before calling
  • Clears the cache for the updated stat to ensure fresh data

Leaderstats Integration

🏆 Automatic Leaderstats

PlayerState now automatically creates and manages leaderstats based on your data configuration:

lua
-- In your DefaultData module, add leaderstats configuration:
local DefaultData = {
    Coins = 0,
    Level = 1,
    Experience = 0,
    Plot = {
        Likes = 0
    },

    -- Leaderstats configuration
    leaderstats = {
        ["💰 Coins"] = "Coins",
        ["⭐ Level"] = "Level",
        ["👍 Likes"] = "Plot.Likes"
    }
}

Features:

  • Automatic Creation: Leaderstats are created when player data loads
  • Real-time Sync: Updates automatically when data changes
  • Type-Safe: Numbers use IntValue, others use StringValue
  • Path Support: Supports nested paths like "Plot.Likes"

Leaderstats Protection

Direct modification of leaderstats paths is prevented. Change the source data instead!

lua
-- ❌ This will fail
PlayerState.Set(player, "leaderstats", {...})

-- ✅ This updates the leaderstat automatically
PlayerState.Set(player, "Coins", 1000)

Usage Examples

💰 Currency Management with Validation

Example
lua
-- Award coins (validation handled internally)
local function awardCoins(player, amount, source)
    local currentCoins = PlayerState.Get(player, "Coins") or 0
    local success = PlayerState.Set(player, "Coins", currentCoins + amount)

    if success then
        print(`Awarded {amount} coins to {player.Name} from {source}`)
        return true
    else
        warn(`Failed to award coins to {player.Name}`)
        return false
    end
end

-- Spend coins with validation
local function spendCoins(player, amount, item)
    local currentCoins = PlayerState.Get(player, "Coins") or 0
    if currentCoins < amount then
        return false, "Insufficient coins"
    end

    local success = PlayerState.Set(player, "Coins", currentCoins - amount)
    if success then
        print(`{player.Name} spent {amount} coins on {item}`)
        return true, "Purchase successful"
    else
        return false, "Transaction failed"
    end
end

📈 Level System with Batch Operations

Example
lua
-- Level up player with batch operations
local function levelUp(player)
    local currentLevel = PlayerState.Get(player, "Level") or 1
    local currentCoins = PlayerState.Get(player, "Coins") or 0
    local bonusCoins = currentLevel * 100

    local success = PlayerState.BatchSetValues(player, {
        {path = "Level", value = currentLevel + 1},
        {path = "Experience", value = 0},
        {path = "Coins", value = currentCoins + bonusCoins}
    })

    if success then
        print(`{player.Name} leveled up to {currentLevel + 1}!`)
        return true
    end

    return false
end

-- Add experience with level up check
local function addExperience(player, amount)
    local currentExp = PlayerState.Get(player, "Experience") or 0
    local currentLevel = PlayerState.Get(player, "Level") or 1
    local expNeeded = currentLevel * 100

    local newExp = currentExp + amount

    if newExp >= expNeeded then
        -- Level up
        levelUp(player)
    else
        -- Just add experience
        PlayerState.Set(player, "Experience", newExp)
    end
end

🎒 Enhanced Inventory Management

Example
lua
local MAX_INVENTORY_SIZE = 50

-- Add item with validation and limits
local function addItem(player, item)
    local inventory = PlayerState.GetPath(player, "Inventory") or {}
    if #inventory >= MAX_INVENTORY_SIZE then
        return false, "Inventory full"
    end

    -- Add timestamp to item
    item.AcquiredTime = os.time()

    local success = PlayerState.AddToArray(player, "Inventory", item)
    if success then
        print(`Added {item.Name} to {player.Name}'s inventory`)
        return true, "Item added"
    end

    return false, "Failed to add item"
end

-- Remove item by ID
local function removeItemById(player, itemId)
    local inventory = PlayerState.GetPath(player, "Inventory") or {}

    for index, item in ipairs(inventory) do
        if item.Id == itemId then
            local success = PlayerState.RemoveFromArray(player, "Inventory", index)
            if success then
                print(`Removed {item.Name} from {player.Name}'s inventory`)
                return true
            end
            break
        end
    end

    return false
end

-- Upgrade item in inventory
local function upgradeItem(player, itemIndex, newStats)
    local inventory = PlayerState.GetPath(player, "Inventory") or {}
    local item = inventory[itemIndex]

    if not item then
        return false, "Item not found"
    end

    -- Merge new stats
    for key, value in pairs(newStats) do
        item[key] = value
    end

    -- Update upgraded time
    item.UpgradedTime = os.time()

    local success = PlayerState.UpdateArrayItem(player, "Inventory", itemIndex, item)
    if success then
        print(`Upgraded {item.Name} for {player.Name}`)
        return true
    end

    return false, "Upgrade failed"
end

🏆 Leaderboard Management

Example
lua
-- Server: Comprehensive leaderboard system with validation
local function setupLeaderboardSystem()
    -- Configuration reminder
    -- Make sure PlayerStateConfig.Server.Leaderboard is properly configured!

    -- ✅ Get top 10 players for coins (works if configured)
    local topCoins = PlayerState.GetLeaderboard("Coins", 10)
    if #topCoins > 0 then
        for rank, entry in ipairs(topCoins) do
            print(`#{rank}: User {entry.userId} - {entry.score} coins`)
        end
    else
        print("No coin leaderboard data available (check configuration)")
    end

    -- ✅ Get player's current rank (returns nil if not ranked)
    local playerRank = PlayerState.GetPlayerRank(player, "Coins")
    if playerRank then
        print(`You're currently ranked #{playerRank} for coins!`)
    else
        print("You're not yet ranked for coins")
    end

    -- ✅ Manual leaderboard update (for special cases)
    local success = PlayerState.UpdateLeaderboard(player, "HighScore", 2500)
    if success then
        print("High score leaderboard updated!")
    else
        print("Failed to update leaderboard (check configuration)")
    end
end

-- Safe leaderboard usage with error handling
local function safeLeaderboardUsage()
    -- Always check if leaderboard is available before heavy usage
    local testBoard = PlayerState.GetLeaderboard("Coins", 1)
    if #testBoard == 0 then
        warn("Leaderboard not configured properly - check PlayerStateConfig")
        return false
    end

    -- Proceed with leaderboard operations
    local playerRank = PlayerState.GetPlayerRank(player, "Coins")
    return PlayerState.UpdateLeaderboard(player, "Coins", 100)
end

-- Get multiple leaderboard stats at once
local function getPlayerStatsSummary(player)
    local stats = {
        coinRank = PlayerState.GetPlayerRank(player, "Coins"),
        levelRank = PlayerState.GetPlayerRank(player, "Level"),
        scoreRank = PlayerState.GetPlayerRank(player, "HighScore"),
    }

    -- Display player's rankings
    for statName, rank in pairs(stats) do
        if rank then
            print(`{statName}: Rank #{rank}`)
        else
            print(`{statName}: Not ranked yet`)
        end
    end

    return stats
end

-- Leaderboard UI data
local function getLeaderboardUIData(statName, maxEntries)
    local leaderboard = PlayerState.GetLeaderboard(statName, maxEntries or 50)
    local uiData = {}

    for i, entry in ipairs(leaderboard) do
        table.insert(uiData, {
            rank = entry.rank,
            userId = entry.userId,
            score = entry.score,
            displayName = "Loading...", -- You'll need to get this from Player service
        })
    end

    return uiData
end

-- Periodic leaderboard announcements
local function announceTopPlayers()
    local topPlayers = PlayerState.GetLeaderboard("Coins", 3)

    if #topPlayers >= 3 then
        print("🏆 TOP 3 RICHEST PLAYERS 🏆")
        for i, entry in ipairs(topPlayers) do
            local medal = i == 1 and "🥇" or i == 2 and "🥈" or "🥉"
            print(`{medal} #{i}: User {entry.userId} - {entry.score} coins`)
        end
    end
end

🏠 Plot Building System

Example
lua
-- Build structure on plot (efficient nested updates)
local function buildStructure(player, buildingType, position)
    local buildingData = {
        Type = buildingType,
        Level = 1,
        Position = position,
        BuiltTime = os.time(),
        Health = 100
    }

    local buildingId = `{buildingType}_{os.time()}`
    local success = PlayerState.SetInDict(player, "Plot.Buildings", buildingId, buildingData)

    if success then
        print(`{player.Name} built {buildingType} at {position}`)
        return true, buildingId
    end

    return false, "Build failed"
end

-- Upgrade building (efficient single-key update)
local function upgradeBuilding(player, buildingId)
    local building = PlayerState.GetPath(player, `Plot.Buildings.{buildingId}`)
    if not building then
        return false, "Building not found"
    end

    building.Level = building.Level + 1
    building.UpgradedTime = os.time()

    local success = PlayerState.SetInDict(player, "Plot.Buildings", buildingId, building)
    if success then
        print(`{player.Name} upgraded {building.Type} to level {building.Level}`)
        return true
    end

    return false, "Upgrade failed"
end

-- Remove building (efficient key removal)
local function removeBuilding(player, buildingId)
    local success = PlayerState.RemoveFromDict(player, "Plot.Buildings", buildingId)
    if success then
        print(`{player.Name} removed building {buildingId}`)
        return true
    end

    return false, "Removal failed"
end

🔄 Event-Driven Data Management

Example
lua
-- Setup event handlers for comprehensive data management
local function setupPlayerStateEvents()
    -- When player data loads
    PlayerState.ProfileLoaded:Connect(function(player, data)
        -- Welcome back message
        local loginCount = (data.Stats.Logins or 0) + 1
        PlayerState.SetPath(player, "Stats.Logins", loginCount)
        PlayerState.Set(player, "LastJoinTime", os.time())

        -- Check for daily rewards
        local lastReward = data.Stats.LastDailyReward or 0
        local currentDay = math.floor(os.time() / 86400) -- Days since epoch

        if currentDay > lastReward then
            PlayerState.SetPath(player, "Stats.LastDailyReward", currentDay)
            PlayerState.Increment(player, "Coins", 100)
            print(`{player.Name} received daily reward!`)
        end

        -- Initialize player systems
        setupPlayerUI(player)
        setupPlayerEffects(player)
    end)

    -- Before data saves
    PlayerState.BeforeSave:Connect(function(player, data)
        -- Final cleanup and validation
        data.LastSaveTime = os.time()

        -- Calculate session playtime
        local joinTime = data.LastJoinTime or os.time()
        local sessionTime = os.time() - joinTime
        local totalPlayTime = (data.Stats.TotalPlayTime or 0) + sessionTime

        PlayerState.SetPath(player, "Stats.TotalPlayTime", totalPlayTime)
        PlayerState.SetPath(player, "Stats.LastSessionTime", sessionTime)

        -- Validate data integrity
        if data.Coins and data.Coins < 0 then
            warn(`{player.Name} had negative coins, resetting to 0`)
            PlayerState.Set(player, "Coins", 0)
        end

        print(`Saving {player.Name}'s data - Session: {sessionTime}s, Total: {totalPlayTime}s`)
    end)

    -- When profile unloads
    PlayerState.ProfileUnloaded:Connect(function(player, data)
        -- Analytics and external system updates
        local finalStats = {
            userId = player.UserId,
            coins = data.Coins or 0,
            level = data.Level or 1,
            totalPlayTime = data.Stats.TotalPlayTime or 0,
            logins = data.Stats.Logins or 0
        }

        -- Send to analytics (direct data access only)
        sendToAnalytics(finalStats)

        -- Cleanup player-specific systems
        cleanupPlayerUI(player)
        cleanupPlayerEffects(player)

        print(`{player.Name} profile unloaded - Final coins: {finalStats.coins}`)
    end)
end

-- Call this during server startup
setupPlayerStateEvents()

Performance Tips

Optimization

Batch Operations: Use BatchSetValues() for multiple changes instead of individual calls Cache Management: Call ClearPathCache() periodically for memory optimization Return Values: Always check success returns for error handling Path Reuse: Reuse path strings when possible for better performance

Best Practices

Automatic Validation: Functions validate automatically - no manual checks needed Leaderstats Protection: Change source data instead of direct leaderstats modification Error Handling: Use proper error handling and provide user feedback Cache Monitoring: Monitor cache performance and clear when necessary

PlayerState - High-Performance Roblox Data Management