Storage
The Storage API provides persistent data storage that survives world restarts and player sessions. Think of it as a database for your world - data is saved permanently until you delete it.
Common uses:
- Player progress (levels completed, achievements unlocked)
- Player preferences (settings, customization choices)
- World state (time of day, resource counts, building placements)
- Leaderboards and high scores
IMPORTANT Limitations:
- SERVER SIDE ONLY: Can only be called from server scripts (not client scripts)
- Rate Limited: Limit calls to avoid RequestThrottled errors (roughly 10-20 calls per second recommended)
- Data Size: Keep individual values reasonable (under 100KB recommended) - use multiple keys for large datasets
- For player items/currency: Use Inventory API instead (it's optimized for that)
Supported data types: string, number, boolean, table (nested tables supported), Vector3
Data Persistence: All data persists permanently across:
- World restarts
- Player sessions
- Server updates Data only deletes when you explicitly call DeleteValue or DeletePlayerValue
Player-specific vs Global:
- Player functions (GetPlayerValue, SetPlayerValue, etc.) automatically prefix with player's user ID
- Global functions use the exact key you provide
- player.user.id = unique permanent ID across all worlds, player.id = temporary session ID (don't use for storage!)
Unity Development: Storage file located at Library/localStorage (can view/edit in text editor for testing)
Methods
Deletes a player-specific value from storage. Equivalent to: DeleteValue(player.user.id .. "/" .. key, callback) For global values or custom key formats, use DeleteValue instead.
Storage.DeletePlayerValue(player, "TempData")
Storage.DeletePlayerValue(player, "Progress", function(error)
if error == StorageError.None then
print("Player progress reset")
end
end)
Parameters
player
key
Returns
Deletes a player-specific value from storage. Equivalent to: DeleteValue(player.user.id .. "/" .. key, callback) For global values or custom key formats, use DeleteValue instead.
Storage.DeletePlayerValue(player, "TempData")
Storage.DeletePlayerValue(player, "Progress", function(error)
if error == StorageError.None then
print("Player progress reset")
end
end)
Parameters
Returns
Deletes a value from storage with the given key.
-- Delete without callback
Storage.DeleteValue("TemporaryData")
-- Delete with callback
Storage.DeleteValue("OldLeaderboard", function(error)
if error == StorageError.None then
print("Value deleted")
end
end)
Parameters
key
Returns
Deletes a value from storage with the given key.
-- Delete without callback
Storage.DeleteValue("TemporaryData")
-- Delete with callback
Storage.DeleteValue("OldLeaderboard", function(error)
if error == StorageError.None then
print("Value deleted")
end
end)
Parameters
key
callback
Returns
Retrieves a player-specific value from storage. Equivalent to: GetValue(player.user.id .. "/" .. key, callback) For global values or custom key formats, use GetValue instead.
Storage.GetPlayerValue(player, "HighScore", function(value, error)
if error == StorageError.None then
print(player.name .. " high score: " .. tostring(value))
end
end)
Parameters
Returns
Retrieves a value from storage with the given key. The callback receives the value and error code. If the key doesn't exist, value is nil and error is StorageError.None.
Storage.GetValue("GlobalHighScore", function(value, error)
if error == StorageError.None then
print("High score: " .. tostring(value))
else
print("Error: " .. tostring(error))
end
end)
Parameters
key
callback
Returns
Increments a player-specific numeric value by the specified amount. Equivalent to: IncrementValue(player.user.id .. "/" .. key, amount, callback) For global values or custom key formats, use IncrementValue instead.
-- Add points to player
Storage.IncrementPlayerValue(player, "Points", 10)
-- Add currency with callback
Storage.IncrementPlayerValue(player, "Coins", 50, function(error)
if error == StorageError.None then
print("Coins added")
end
end)
Parameters
Returns
Increments a player-specific numeric value by the specified amount. Equivalent to: IncrementValue(player.user.id .. "/" .. key, amount, callback) For global values or custom key formats, use IncrementValue instead.
-- Add points to player
Storage.IncrementPlayerValue(player, "Points", 10)
-- Add currency with callback
Storage.IncrementPlayerValue(player, "Coins", 50, function(error)
if error == StorageError.None then
print("Coins added")
end
end)
Parameters
Returns
Increments a numeric value by the specified amount. If the value doesn't exist, it will be set to the amount. Safe for concurrent access - use this instead of UpdateValue for simple number increments.
-- Increment global player count
Storage.IncrementValue("TotalPlayers", 1)
-- Decrement with callback
Storage.IncrementValue("AvailableSlots", -1, function(error)
if error == StorageError.None then
print("Slot taken")
end
end)
Parameters
key
amount
Returns
Increments a numeric value by the specified amount. If the value doesn't exist, it will be set to the amount. Safe for concurrent access - use this instead of UpdateValue for simple number increments.
-- Increment global player count
Storage.IncrementValue("TotalPlayers", 1)
-- Decrement with callback
Storage.IncrementValue("AvailableSlots", -1, function(error)
if error == StorageError.None then
print("Slot taken")
end
end)
Parameters
key
amount
callback
Returns
Searches for player-specific values that start with the given key prefix. Equivalent to: SearchValue(player.user.id .. "/" .. key, limit, cursorId, callback) For global values or custom key formats, use SearchValue instead.
-- Search player's saved levels
Storage.SearchPlayerValue(player, "level_", 5, nil, function(values, cursorId, error)
if error == StorageError.None then
print("Found " .. #values .. " saved levels")
for i, entry in ipairs(values) do
print("Level: " .. tostring(entry))
end
end
end)
Parameters
player
key
limit
cursorId
callback
Returns
Searches for values that start with the given key prefix. Returns up to 'limit' most recent matching values.
Pagination: Pass nil for cursorId to get first results. Use returned cursorId for next page. If returned cursorId is nil, no more results exist.
Callback receives: array of values (tables with key-value pairs), cursorId, error
-- Search for all player scores
Storage.SearchValue("player_scores/", 10, nil, function(values, cursorId, error)
if error == StorageError.None then
for i, entry in ipairs(values) do
print("Found: " .. tostring(entry))
end
-- Get next page if available
if cursorId ~= nil then
-- Call SearchValue again with cursorId for next page
end
end
end)
Parameters
key
limit
cursorId
callback
Returns
Sets a player-specific value in storage. Equivalent to: SetValue(player.user.id .. "/" .. key, value, callback) For global values or custom key formats, use SetValue instead.
Storage.SetPlayerValue(player, "Level", 5)
Storage.SetPlayerValue(player, "Inventory", { coins = 100, gems = 5 }, function(error)
if error == StorageError.None then
print("Player data saved")
end
end)
Parameters
Returns
Sets a player-specific value in storage. Equivalent to: SetValue(player.user.id .. "/" .. key, value, callback) For global values or custom key formats, use SetValue instead.
Storage.SetPlayerValue(player, "Level", 5)
Storage.SetPlayerValue(player, "Inventory", { coins = 100, gems = 5 }, function(error)
if error == StorageError.None then
print("Player data saved")
end
end)
Parameters
Returns
Sets a value in storage with the given key. Supported types: string, number, boolean, table, Vector3
CAUTION: For values that depend on previous values (e.g., leaderboards), use UpdateValue to prevent data loss. For incrementing/decrementing numbers, use IncrementValue instead.
-- Without callback
Storage.SetValue("GameMode", "TeamDeathmatch")
-- With callback
Storage.SetValue("ServerConfig", { maxPlayers = 10, friendlyFire = false }, function(error)
if error == StorageError.None then
print("Config saved successfully")
end
end)
Parameters
key
value
Returns
Sets a value in storage with the given key. Supported types: string, number, boolean, table, Vector3
CAUTION: For values that depend on previous values (e.g., leaderboards), use UpdateValue to prevent data loss. For incrementing/decrementing numbers, use IncrementValue instead.
-- Without callback
Storage.SetValue("GameMode", "TeamDeathmatch")
-- With callback
Storage.SetValue("ServerConfig", { maxPlayers = 10, friendlyFire = false }, function(error)
if error == StorageError.None then
print("Config saved successfully")
end
end)
Parameters
key
value
callback
Returns
Safely updates a player-specific value based on its current state. Equivalent to: UpdateValue(player.user.id .. "/" .. key, modifier, callback) For global values or custom key formats, use UpdateValue instead.
Storage.UpdatePlayerValue(player, "Stats", function(currentStats)
if currentStats == nil then
return { kills = 1, deaths = 0 }
else
currentStats.kills = currentStats.kills + 1
return currentStats
end
end)
Parameters
Returns
Safely updates a player-specific value based on its current state. Equivalent to: UpdateValue(player.user.id .. "/" .. key, modifier, callback) For global values or custom key formats, use UpdateValue instead.
Storage.UpdatePlayerValue(player, "Stats", function(currentStats)
if currentStats == nil then
return { kills = 1, deaths = 0 }
else
currentStats.kills = currentStats.kills + 1
return currentStats
end
end)
Parameters
Returns
Safely updates a value based on its current state - prevents data loss from simultaneous updates.
Why use this instead of Get then Set:
- PROBLEM: If two players trigger updates at the same time, one update can be lost Example: Player A and B both try to add their score to leaderboard Player A reads 100, adds 50 = 150 Player B reads 100 (before A saves), adds 30 = 130 Result: Only 130 saved, Player A's 50 points lost!
- SOLUTION: UpdateValue ensures both updates apply correctly (result = 180)
How it works:
- Retrieves current value (nil if doesn't exist)
- Calls your modifier function with current value
- Saves the value your modifier returns
- If another update happened simultaneously, retries automatically with new current value
The modifier may be called multiple times if concurrent updates detected. Return nil from modifier to cancel the update (no change saved).
When to use:
- Leaderboards (adding new high scores)
- Counters (tracking total events)
- Lists (adding items to shared arrays)
- Any value that depends on its previous value
When NOT to use:
- Simple overwrites (use SetValue - it's faster)
- Number increments (use IncrementValue - it's optimized for this)
-- Update leaderboard with new high score
Storage.UpdateValue("GlobalHighScore", function(currentScore)
local newScore = 1000
if currentScore == nil or newScore > currentScore then
return newScore
else
return nil -- Don't update if score isn't higher
end
end, function(error)
if error == StorageError.None then
print("High score updated")
end
end)
Parameters
key
modifier
Returns
Safely updates a value based on its current state - prevents data loss from simultaneous updates.
Why use this instead of Get then Set:
- PROBLEM: If two players trigger updates at the same time, one update can be lost Example: Player A and B both try to add their score to leaderboard Player A reads 100, adds 50 = 150 Player B reads 100 (before A saves), adds 30 = 130 Result: Only 130 saved, Player A's 50 points lost!
- SOLUTION: UpdateValue ensures both updates apply correctly (result = 180)
How it works:
- Retrieves current value (nil if doesn't exist)
- Calls your modifier function with current value
- Saves the value your modifier returns
- If another update happened simultaneously, retries automatically with new current value
The modifier may be called multiple times if concurrent updates detected. Return nil from modifier to cancel the update (no change saved).
When to use:
- Leaderboards (adding new high scores)
- Counters (tracking total events)
- Lists (adding items to shared arrays)
- Any value that depends on its previous value
When NOT to use:
- Simple overwrites (use SetValue - it's faster)
- Number increments (use IncrementValue - it's optimized for this)
-- Update leaderboard with new high score
Storage.UpdateValue("GlobalHighScore", function(currentScore)
local newScore = 1000
if currentScore == nil or newScore > currentScore then
return newScore
else
return nil -- Don't update if score isn't higher
end
end, function(error)
if error == StorageError.None then
print("High score updated")
end
end)
Parameters
key
modifier
callback
Returns
Updated 1 day ago