Skip to content

Configuration

Detailed reference for PlayerStateConfig, tuning, performance options, and leaderboards. For server/client initialization and DefaultData, see Setup.

PlayerStateConfig Module

PlayerState now includes a centralized configuration module that allows you to customize system behavior without modifying the core implementation files.

Location

lua
-- ReplicatedStorage.Libraries.PlayerState.PlayerStateConfig
local Config = require(ReplicatedStorage.Libraries.PlayerState.PlayerStateConfig)

Configuration Structure

The Configuration module separates settings for server and client environments:

lua
local DefaultData = require(script.Parent.DefaultData)

local Config = {
    Server = {
        Profile = {
            Key = "PlayerData",
            Template = DefaultData,
        },

        DataStore = {
            Name = "PlayerData_0.01",
            Scope = "Production",
        },

        LegacyDataStore = {
            enabled = false,
            store = "OldStore",
            strategy = "Auto",
            keys = { "{UserId}", "PlayerData_{UserId}" },
            advanced = {
                scope = nil,
                marker = {
                    path = "_Migration.LegacyDataStoreV1",
                    writeOnNoData = true,
                },
                payload = { mode = "auto" },
                manual = { transform = nil },
                fetch = { custom = nil },
            },
        },

        ViewedUser = {
            ViewedUserId = 0,
            ReadOnly = true,
        },

        CleanExtraFields = false,
        AutoCloneTables = false,
        DataWaitTimeout = 10,

        RuntimeNonPersistentRoots = {
            "session",
            "_LeaderboardRanks",
            "_Leaderboards",
            "_Leaderboard",
        },

        ServerOnlyRoots = {
            "Server",
        },

        SharedSession = {
            round = {
                phase = "Waiting",
                endsAt = 0,
            },
        },

        BatchDelay = 0.03,
        BatchSize = 20,
        MaxCacheSize = 1000,

        Leaderboard = {
            Enabled = true,
            DataStoreName = "PlayerState_Leaderboards",
            TrackedStats = { "Coins" },
            SyncInterval = 60,
            FullLeaderboardWriteInterval = 600,
            UpdateOnPlayerLeave = true,
        },
    },

    Client = {
        CacheDuration = 0.1,
        CacheCleanupInterval = 30,
        MaxCacheSize = 1000,
        MaxPathCacheSize = 1000,
        AutoCloneTables = false,
    },
}

return Config

See Migration Guide for LegacyDataStore options. See Session Data for RuntimeNonPersistentRoots. See Shared Session for SharedSession and Server-only data for ServerOnlyRoots. The data visibility model summarizes how these relate.

Configuration Notes

  • Modify these values in your PlayerStateConfig.lua file to customize behavior
  • All settings have sensible defaults - only change what you need to customize
  • Test in development before deploying configuration changes to production
  • Monitor performance and adjust batch/cache settings based on your game's needs

Server Configuration Options

SettingDefaultDescription
Profile.Key"PlayerData"Unique identifier for ProfileStore data
Profile.TemplateDefaultDataData structure template (auto-populated)
DataStore.Name"PlayerData_0.01"DataStore name for versioning
DataStore.Scope"Production"Data scope ("Production" or "Testing")
ViewedUser.ViewedUserId0Studio-only: load another user's profile for debugging
ViewedUser.ReadOnlytrueWhen viewing another user, block all write APIs
CleanExtraFieldsfalseRemove fields not in template during reconciliation
DataWaitTimeout10Seconds to wait for player data before timeout
BatchDelay0.03Batch processing delay in seconds
BatchSize20Operations to queue before auto-flush
MaxCacheSize1000Maximum cached path keys
AutoCloneTablesfalseAutomatically deep clone tables from Get/GetPath/GetFromDict
RuntimeNonPersistentRoots{"session", "_LeaderboardRanks", ...}Top-level keys that replicate but never persist
ServerOnlyRoots{} (example: {"Server"})Top-level keys that persist but are stripped before the player Replica (never replicated)
SharedSession{} (template table)Default server-wide shared session state; not saved; replicated to all clients in this server. No nested Template — this table is the template
LegacyDataStoreenabled = false (default)Import from old DataStore; enable when ready. See Migration
Leaderboard.EnabledtrueEnable/disable leaderboard functionality
Leaderboard.DataStoreName"PlayerState_Leaderboards"DataStore for leaderboard data
Leaderboard.TrackedStats{"Coins", "Level", ...}Stats to track on leaderboards
Leaderboard.SyncInterval60Leaderboard sync frequency in seconds (minimum enforced: 60)
Leaderboard.FullLeaderboardWriteInterval600 (example) or 0Seconds between full leaderboard write sweeps (online players × tracked stats). 0 disables the periodic sweep. Minimum 60 when enabled. Joins, stat changes, leaves, and shutdown still update scores
Leaderboard.UpdateOnPlayerLeavetrueUpdate leaderboards when players leave

Detailed Configuration Explanations

Server Settings

Profile.Key

  • Unique identifier for your game's ProfileStore data
  • Use different keys for different games or major versions
  • Should be consistent across all servers in your game

Profile.Template

  • Automatically populated from your DefaultData module
  • Defines the structure of player data
  • Used for data reconciliation when loading profiles

DataStore.Name

  • Roblox DataStore name for storing player data
  • Increment this when making breaking data structure changes
  • Use different names for development vs production environments

DataStore.Scope

  • "Production" for live player data
  • "Testing" for development/mock data (uses ProfileStore.Mock)
  • Automatically switches to testing mode when scope is "Testing"

ViewedUser (Studio-only)

  • Lets you load another player's profile in Roblox Studio for debugging (e.g. test with a specific user's saved data).
  • ViewedUserId: 0 = disabled (each player loads their own profile). Any non-zero UserId loads that user's profile in Studio only; in live games this is ignored and the player's own profile is used.
  • ReadOnly: When viewing another user's profile, true blocks all mutation APIs (Set, SetPath, SetValues, batch ops, Increment, Decrement, array/dict writes, WipePlayerData, etc.). Reads and replica subscriptions still work. Set to false only if you need to modify the viewed profile from Studio (use with care).
  • On init, PlayerState prints whether the session is loading the viewed profile in read-only or writable mode.

CleanExtraFields

  • false = Keep extra fields not in template (safer)
  • true = Remove fields not in template during reconciliation
  • Set to true for cleaner data, but may lose custom fields

DataWaitTimeout

  • Seconds to wait for player data to load before timing out
  • Player gets kicked if data doesn't load within this time
  • Increase for slower DataStore responses, decrease for faster failure

Batch Processing

  • BatchDelay: How long to wait before processing queued operations (0.01-0.1 recommended)
  • BatchSize: Maximum operations to queue before auto-processing (10-50 recommended)
  • Lower delay = faster response, higher CPU usage
  • Higher batch size = more efficient, potentially higher memory usage

MaxCacheSize

  • Maximum number of cached path keys for performance
  • Higher values = better performance, more memory usage
  • Lower values = less memory, slightly slower path parsing

AutoCloneTables

  • false = Return table references (default, more performant)
  • true = Automatically deep clone tables from Get(), GetPath(), and GetFromDict()
  • When enabled, prevents accidental modification of internal data structures
  • Slightly higher memory usage but safer for data manipulation
  • Use when you frequently modify returned table values

RuntimeNonPersistentRoots

  • Top-level keys whose data replicates to clients but is never persisted
  • Default includes session (user-defined) plus _LeaderboardRanks, _Leaderboards, _Leaderboard (internal)
  • Values are stripped before save and re-seeded from template on load
  • SetOfflineData blocks writes to these paths
  • See Session Data for usage

ServerOnlyRoots

  • Top-level keys that stay in profile.Data (saved and loaded) but are removed when building the per-player client Replica
  • No client receives these roots—not even the owning player
  • Separate from RuntimeNonPersistentRoots (those are not saved; server-only roots are saved)
  • SetOfflineData may update paths under these roots because they are persistent
  • See Server-only data

SharedSession

  • Defines the default template for server-wide temporary state for the current server only
  • Not persisted to the DataStore; exists only for the running server instance
  • Replicated to every client in this server via a dedicated SharedSession Replica (not the player Replica)
  • No Profile.Template-style nesting: the SharedSession table in config is the template
  • Use for round state, match phase, server-local timers, votes—not for universe-wide or cross-server data
  • See Shared Session

LegacyDataStore

  • Import data from an old DataStore during profile load
  • enabled = false by default; set to true when ready to migrate
  • Requires store, strategy, keys when enabled
  • See Migration Guide for full configuration

Leaderboard Settings

Leaderboard.Enabled

  • Must be true for any leaderboard functionality to work
  • Functions return default values when disabled
  • Set to false to temporarily disable leaderboards

Leaderboard.DataStoreName

  • Base name for leaderboard DataStores
  • Each tracked stat creates: {DataStoreName}_{statName}
  • Example: "Coins" becomes "PlayerState_Leaderboards_Coins"

Leaderboard.TrackedStats

  • Array of data paths to track on leaderboards
  • Only numeric values can be tracked
  • Supports nested paths: "Plot.Likes", "Stats.TotalPlayTime"
  • Empty array disables leaderboard tracking

Leaderboard.SyncInterval

  • How often to sync leaderboard data (seconds)
  • PlayerState enforces a minimum of 60 seconds at runtime if configured lower
  • Lower values = more frequent updates, higher DataStore usage
  • Consider your game's scale and DataStore limits

Leaderboard.FullLeaderboardWriteInterval

  • Optional periodic full sweep that writes leaderboard scores for every online player × every tracked stat (heavy vs incremental updates)
  • 0 = disabled (default behavior if omitted)
  • When set to a positive number: interval in seconds between sweeps; runtime enforces minimum 60 if lower
  • Incremental paths still run: joins, tracked stat changes, player leave, and server shutdown updates are not gated by this setting

Leaderboard.UpdateOnPlayerLeave

  • true = Update leaderboards when players leave (recommended)
  • false = Only update during sync intervals
  • Immediate updates provide better user experience

Client Settings

CacheDuration

  • How long to cache values before checking for updates (seconds)
  • Lower values = more responsive, higher network usage
  • Higher values = less responsive, lower network usage

CacheCleanupInterval

  • How often to clean up expired cache entries (seconds)
  • Lower values = more frequent cleanup, slightly higher CPU usage
  • Higher values = less frequent cleanup, slightly higher memory usage

MaxCacheSize & MaxPathCacheSize

  • Maximum cached items before cleanup
  • Higher values = better performance, more memory usage
  • Lower values = less memory, may cause more cache misses

Client Configuration Options

SettingDefaultDescription
CacheDuration0.1Value cache duration in seconds
CacheCleanupInterval30Cache cleanup frequency in seconds
MaxCacheSize1000Maximum cached values
MaxPathCacheSize1000Maximum cached path strings
AutoCloneTablesfalseDeep clone tables from Get/GetPath/GetFromDict

Customizing Configuration

To customize settings, modify the Configuration module values:

lua
-- PlayerStateConfig.lua
local Config = {
    Server = {
        DataStore = {
            -- Use different DataStore names for different environments
            Name = game.PlaceId == 123456789 and "PlayerData_DEV" or "PlayerData_PROD",
            Scope = game.PlaceId == 123456789 and "Testing" or "Production",
        },

        -- Development: Faster batching for testing
        BatchDelay = game.PlaceId == 123456789 and 0.01 or 0.03,
        BatchSize = game.PlaceId == 123456789 and 5 or 20,
    },

    Client = {
        -- Development: Shorter cache for faster testing
        CacheDuration = game.PlaceId == 123456789 and 0.05 or 0.1,
    },
}
lua
-- PlayerStateConfig.lua
local Config = {
    Server = {
        -- High-performance game settings
        BatchDelay = 0.02,      -- Faster batching
        BatchSize = 30,         -- Larger batches
        MaxCacheSize = 2000,    -- More caching
    },

    Client = {
        -- Optimize for memory-conscious environments
        CacheDuration = 0.2,           -- Longer cache duration
        CacheCleanupInterval = 60,     -- Less frequent cleanup
        MaxCacheSize = 500,            -- Smaller cache
        MaxPathCacheSize = 500,
    },
}
lua
-- PlayerStateConfig.lua
local Config = {
    Server = {
        -- Conservative memory usage
        BatchDelay = 0.05,      -- Less frequent batching
        BatchSize = 10,         -- Smaller batches
        MaxCacheSize = 500,     -- Reduced caching
        CleanExtraFields = true, -- Clean up unused data
    },

    Client = {
        -- Minimal memory footprint
        CacheDuration = 0.05,          -- Short cache duration
        CacheCleanupInterval = 15,     -- Frequent cleanup
        MaxCacheSize = 250,            -- Small cache
        MaxPathCacheSize = 250,
    },
}
lua
-- PlayerStateConfig.lua - For games with many concurrent players
local Config = {
    Server = {
        -- Optimize for high traffic
        BatchDelay = 0.02,      -- Faster batching for responsiveness
        BatchSize = 50,         -- Larger batches for efficiency
        MaxCacheSize = 2000,    -- More caching for performance
        DataWaitTimeout = 15,   -- Longer timeout for slower responses

        Leaderboard = {
            Enabled = true,
            DataStoreName = "PlayerState_Leaderboards",
            TrackedStats = {"Coins", "Level", "HighScore"},
            SyncInterval = 120,        -- Longer interval to reduce DataStore load
            UpdateOnPlayerLeave = true,
        },
    },

    Client = {
        -- Balanced caching for high traffic
        CacheDuration = 0.2,           -- Longer cache to reduce network calls
        CacheCleanupInterval = 60,     -- Less frequent cleanup
        MaxCacheSize = 2000,           -- More caching for performance
        MaxPathCacheSize = 2000,
    },
}
lua
-- PlayerStateConfig.lua - Development environment
local Config = {
    Server = {
        Profile = {
            Key = "PlayerData_DEV",
        },
        DataStore = {
            Name = "PlayerData_DEV",
            Scope = "Testing",  -- Use mock data for development
        },

        -- Development-friendly settings
        BatchDelay = 0.01,      -- Very fast batching for testing
        BatchSize = 5,          -- Small batches for immediate feedback
        DataWaitTimeout = 5,    -- Fail fast for development

        Leaderboard = {
            Enabled = true,
            DataStoreName = "PlayerState_Leaderboards_DEV",
            TrackedStats = {"Coins", "Level", "HighScore"},
            SyncInterval = 30,         -- Faster sync for development
            UpdateOnPlayerLeave = true,
        },
    },

    Client = {
        -- Development settings for faster iteration
        CacheDuration = 0.05,          -- Short cache for testing changes
        CacheCleanupInterval = 10,     -- Frequent cleanup
        MaxCacheSize = 500,            -- Smaller cache for development
        MaxPathCacheSize = 500,
    },
}

Configuration Tips

Performance Optimization

  • Lower BatchDelay: Faster response, higher CPU usage
  • Higher BatchSize: More efficient batching, potentially higher memory usage
  • Larger cache sizes: Better performance, more memory usage

Leaderboard Optimization

  • SyncInterval ≥ 60 seconds: Respect DataStore limits
  • UpdateOnPlayerLeave = true: Immediate updates for better UX
  • Limit TrackedStats: Only track stats you actually display
  • Use nested paths: "Stats.TotalPlayTime" instead of separate stats

Development Best Practices

  • Use different DataStore names for dev vs production
  • Set Scope = "Testing" for development with mock data
  • Lower SyncInterval in development for faster testing
  • Check console warnings for configuration issues

DataStore Versioning

Always increment DataStore.Name when making breaking changes to your data structure to avoid data corruption.

Leaderboard DataStore Limits

Roblox DataStore limits apply to leaderboards:

  • 60 requests per minute per player
  • Leaderboard updates count toward this limit
  • Consider your game's scale when configuring SyncInterval
  • Monitor DataStore usage in production
Advanced Configuration
lua
-- Example: Conditional configuration based on server region
local Config = {
    Server = {
        DataStore = {
            Name = "PlayerData_" .. (game.JobId:sub(1,8) or "default"),
            Scope = game.PrivateServerId and "Private" or "Production",
        },

        -- Adjust performance based on server load
        BatchDelay = game.Players.MaxPlayers > 20 and 0.05 or 0.03,
        BatchSize = game.Players.MaxPlayers > 20 and 30 or 20,
        MaxCacheSize = game.Players.MaxPlayers > 20 and 1500 or 1000,
    },
}

Troubleshooting Configuration Issues

Common Configuration Problems

Problem: Leaderboard functions return empty arrays or nil

lua
-- ❌ This happens when leaderboard is disabled
local leaderboard = PlayerState.GetLeaderboard("Coins", 10) -- Returns {}

Solution: Check your configuration:

lua
-- Make sure these are set correctly:
Leaderboard = {
    Enabled = true,           -- Must be true
    TrackedStats = {"Coins"}, -- Must include the stat you're querying
}

Problem: "Stat is not configured" warnings in console

[PlayerState] Stat "Experience" is not configured as a tracked leaderboard stat

Solution: Add the stat to TrackedStats:

lua
TrackedStats = {
    "Coins",
    "Experience",  -- Add missing stats here
    "Level",
}

Problem: DataStore limit errors

DataStore request was rejected due to rate limiting

Solution: Increase SyncInterval:

lua
Leaderboard = {
    SyncInterval = 120,  -- Increase from 60 to reduce DataStore calls
}

Problem: Players getting kicked with "Failed to load data"

PlayerState: Timeout waiting for player's data to load

Solution: Increase DataWaitTimeout:

lua
DataWaitTimeout = 20,  -- Increase from 10 for slower DataStore responses

Problem: Leaderboard not updating immediately

Player's leaderboard rank doesn't update after stat changes

Solution: Enable UpdateOnPlayerLeave or use manual updates:

lua
-- Option 1: Enable immediate updates
UpdateOnPlayerLeave = true,

-- Option 2: Manual update when needed
PlayerState.UpdateLeaderboard(player, "Coins", newCoinValue)
Debugging Configuration

Check your current configuration at runtime:

lua
-- Server Script - Debug current configuration
local Config = require(game.ReplicatedStorage.Libraries.PlayerState.PlayerStateConfig)

print("=== PlayerState Configuration Debug ===")
print("Leaderboard Enabled:", Config.Server.Leaderboard.Enabled)
print("Tracked Stats:", table.concat(Config.Server.Leaderboard.TrackedStats, ", "))
print("Sync Interval:", Config.Server.Leaderboard.SyncInterval)
print("DataStore Name:", Config.Server.Leaderboard.DataStoreName)
print("Batch Delay:", Config.Server.BatchDelay)
print("Batch Size:", Config.Server.BatchSize)

Test leaderboard functionality:

lua
-- Server Script - Test leaderboard configuration
local function testLeaderboardConfig()
    print("Testing leaderboard configuration...")

    -- Test if leaderboard is enabled
    local testData = PlayerState.GetLeaderboard("Coins", 1)
    if #testData == 0 then
        warn("Leaderboard not working - check configuration!")
        return false
    end

    print("✅ Leaderboard configuration is working")
    return true
end

Monitor DataStore usage:

lua
-- Server Script - Monitor DataStore requests
local dataStoreRequests = 0

-- Hook into ProfileStore to count requests (if available)
-- This helps you stay within DataStore limits

game:BindToClose(function()
    print(`Total DataStore requests this session: {dataStoreRequests}`)
end)

The Configuration module ensures consistent behavior across your game while allowing easy customization for different environments and performance requirements.

Critical: DataStore Limits

Be aware of Roblox DataStore limits when configuring leaderboards:

  • 60 requests per minute per player (including leaderboards)
  • SyncInterval should be ≥ 60 seconds for optimal performance
  • Leaderboard updates count toward your DataStore quota
  • Consider your game's scale before lowering SyncInterval

Runtime Validation

PlayerState now includes runtime configuration validation:

  • Leaderboard functions validate configuration at runtime
  • Misconfigured settings trigger console warnings
  • Functions return safe defaults when configuration is invalid
  • Check console output for configuration issues during development

🏆 Leaderboard Configuration

Important: Leaderboard Config Required

You MUST configure the Leaderboard section in PlayerStateConfig for leaderboard functionality to work!

Without proper leaderboard configuration, all leaderboard functions will return nil or empty results.

Basic Leaderboard Setup

lua
-- PlayerStateConfig.lua
local Config = {
    Server = {
        Leaderboard = {
            Enabled = true,                    -- Required: Enable leaderboards
            DataStoreName = "PlayerState_Leaderboards", -- DataStore for leaderboard data
            TrackedStats = {                   -- Required: Stats to track
                "Coins",                       -- Track coin count
                "Level",                       -- Track player level
                "HighScore",                   -- Track high scores
                "Plot.Likes",                  -- Track plot likes (nested path)
                "Stats.TotalPlayTime",         -- Track total playtime
            },
            SyncInterval = 60,                -- How often to sync (seconds)
            UpdateOnPlayerLeave = true,       -- Update when player leaves
        },
    },
}

Leaderboard Configuration Options

SettingDefaultDescription
EnabledtrueMust be true for leaderboards to work
DataStoreName"PlayerState_Leaderboards"DataStore for leaderboard data
TrackedStats{"Coins", "Level", ...}Array of data paths to track
SyncInterval60How often to sync leaderboard data (seconds)
UpdateOnPlayerLeavetrueUpdate leaderboards when players leave

TrackedStats Configuration

The TrackedStats array defines which data fields should be tracked on leaderboards:

lua
TrackedStats = {
    -- Top-level stats
    "Coins",           -- Tracks player.Coins
    "Level",           -- Tracks player.Level
    "HighScore",       -- Tracks player.HighScore

    -- Nested stats (using dot notation)
    "Plot.Likes",      -- Tracks player.Plot.Likes
    "Stats.TotalPlayTime",  -- Tracks player.Stats.TotalPlayTime
    "Inventory.Count", -- Tracks player.Inventory.Count

    -- Any numeric data path
    "Achievements.Total",    -- Custom achievement tracking
}

Important Notes

  • Only numeric values can be tracked on leaderboards
  • Paths must exist in your DefaultData structure
  • Nested paths are supported using dot notation
  • Empty array disables leaderboard tracking

Leaderboard DataStore

Each tracked stat creates its own OrderedDataStore:

  • "Coins""PlayerState_Leaderboards_Coins"
  • "Plot.Likes""PlayerState_Leaderboards_Plot_Likes"
  • "Stats.TotalPlayTime""PlayerState_Leaderboards_Stats_TotalPlayTime"

DataStore Limits

Remember Roblox DataStore limits:

  • 60 requests per minute per player
  • Consider your game's scale when setting SyncInterval

Automatic vs Manual Updates

Automatic Updates (recommended):

  • Leaderboards update automatically when tracked data changes
  • No additional code needed
  • Happens during SyncInterval or when players leave

Manual Updates (for special cases):

lua
-- Force immediate leaderboard update
PlayerState.UpdateLeaderboard(player, "Coins", 5000)

Testing Leaderboards

For testing without affecting live data:

lua
-- Testing configuration
Leaderboard = {
    Enabled = true,
    DataStoreName = "PlayerState_Leaderboards_TEST",
    TrackedStats = {"Coins", "Level"},
    SyncInterval = 30,                    -- Faster sync for testing
    UpdateOnPlayerLeave = true,
}

Runtime Validation

PlayerState now includes runtime validation for all leaderboard functions:

lua
-- ✅ This will work (Coins is in TrackedStats)
local leaderboard = PlayerState.GetLeaderboard("Coins", 10)

-- ❌ This will return empty array and warn (Level not in TrackedStats)
local levelBoard = PlayerState.GetLeaderboard("Level", 10) -- Returns {}, warns in console

-- ❌ This will return false and warn (leaderboard disabled)
local success = PlayerState.UpdateLeaderboard(player, "Coins", 100) -- Returns false, warns in console

Validation Rules:

  • Functions check CONFIG.Leaderboard.Enabled at runtime
  • Functions validate stat names against CONFIG.Leaderboard.TrackedStats
  • Invalid configurations return appropriate failure values and log warnings
  • No crashes - graceful degradation with clear error messages

PlayerState - High-Performance Roblox Data Management