Easy Player Data Saving (SimpleStore v2.0)
Have you ever wanted to use DataStores but got lost in the confusing API calls, retries, or table serialization? Don’t worry — this simple DataHandler module takes care of everything for you.
It automatically saves, loads, and builds your player data structure just by giving it a single template folder. No more writing save functions or worrying about missing values — everything just works out of the box.
What It Does
Once installed, the system automatically:
- Clones your DataTemplate into each player when they join
- Loads their saved data from Roblox DataStore
- Automatically saves data when they leave
- Autosaves every 60 seconds (configurable)
- Supports both normal folders and dynamic container folders (for things like Pets, Weapons, Characters, etc.)
- Rebuilds missing folders or values if they ever get deleted
- Has retry logic so failed saves don’t just vanish
All you have to do is set up your DataTemplate, require the module, and call DataHandler:Init().
Setup
- Create a folder in ServerStorage called DataTemplate.
Inside it, make your value objects and subfolders exactly how you want them to appear in each player.
Example:
DataTemplate
├── Stats
│ ├── Level (IntValue)
│ └── Gold (IntValue)
├── PetsFolder
│ ├── PetTemplate (Folder)
│ │ ├── Shiny (BoolValue)
│ │ ├── Equipped (BoolValue)
│ │ ├── Level (IntValue)
│ │ ├── EXP (IntValue)
│ │ └── PetName (StringValue)
│ └── (Attributes: IsContainer = true, TemplateName = "PetTemplate")
├── Characters
│ ├── CharacterTemplate (Folder)
│ │ ├── HP (IntValue)
│ │ ├── Stamina (IntValue)
│ │ ├── Name (StringValue)
│ │ └── EquippedWeapon (StringValue)
│ └── (Attributes: IsContainer = true, TemplateName = "CharacterTemplate")
Containers are special folders with the attribute IsContainer = true.
They can dynamically clone items (like Pets, Characters, etc.) from their internal template.
Example Usage
Once DataHandler is initialized, it automatically handles all saving and loading — you just work with the player’s data like normal Roblox instances.
Here are some practical examples:
local DataHandler = require(game.ServerScriptService.DataHandler)
DataHandler:Init()
-- 🪙 Add gold to a player
local playerData = game.Players.["Player Name"].PlayerData
local stats = playerData:WaitForChild("Stats")
stats.Gold.Value += 100 -- adds 100 gold
print("New gold:", stats.Gold.Value)
-- The system will automatically detect this change
-- and save it on the next autosave tick.
Working With Container Folders (Pets, Characters, etc.)
You might’ve noticed folders like PetsFolder and Characters in the template example.
These are special container folders, perfect for systems where you need to store multiple entries dynamically, like:
- Player pets
- Weapons inventory
- Owned characters or skins
Each container folder needs two attributes:
IsContainer = trueTemplateName = "NameOfYourTemplateFolder"
When the player joins, the DataHandler looks for these containers and uses their template to automatically rebuild entries that were saved previously.
For example:
DataTemplate
└── PetsFolder
├── PetTemplate
│ ├── Shiny (BoolValue)
│ ├── Equipped (BoolValue)
│ ├── Level (IntValue)
│ ├── EXP (IntValue)
│ └── PetName (StringValue)
└── (Attributes: IsContainer = true, TemplateName = "PetTemplate")
Then, if you want to give the player a new pet:
local playerData = game.Players["player name"].PlayerData
local petsFolder = playerData:WaitForChild("PetsFolder")
local petTemplate = game.ServerStorage.DataTemplate.PetsFolder:WaitForChild("PetTemplate")
-- Create a new pet
local newPet = petTemplate:Clone()
newPet.PetName.Value = "Fluffy"
newPet.Level.Value = 1
newPet.Parent = petsFolder
newPet.Name = RandomNumber() --Or a name of your choice, i recommend them being unique
That’s it. The DataHandler will automatically include Fluffy in the player’s save data, along with its level, XP, and attributes.
When the player rejoins, that same pet will be rebuilt from the saved data with all its properties intact.
Extra Info
- Default autosave interval: 60 seconds (you can change this)
- Maximum retry attempts per save: 5
- Works perfectly with folders and ValueBase objects
- Fully recursive — handles deeply nested data structures
- Automatically adds a
LoadedBoolValue inside the player as (player.Loaded) when data finishes loading (which I recommend checking before grabbing player data)
Get The Module
Here is the full script. Simply make a Module Script, and then paste this code into it:
--[[
--> Kyonabc <--
Simple Store (v2.0)
----------------------------------------
📦 Example Usage:
local SimpleStore = require(game.ReplicatedStorage.DataHandler.SimpleStore
-- Force save for a player
SimpleStore:SaveData(player)
-- Debug player data
print(SimpleStore:GetData(player))
----------------------------------------
💡 Summary:
- Clones your DataTemplate from ServerStorage under each player.
- Handles both regular folders and *dynamic container folders*.
- Containers (e.g. PetsFolder, Characters) can define their own item templates.
- Automatically detects and rebuilds folders & value objects at runtime.
- Includes autosave, retry logic, and detailed logs.
----------------------------------------
📂 Example DataTemplate layout (ServerStorage/DataTemplate)
DataTemplate
├── Stats
│ ├── Level (IntValue)
│ └── Gold (IntValue)
├── PetsFolder
│ ├── PetTemplate (Folder)
│ │ ├── Shiny (BoolValue)
│ │ ├── Equipped (BoolValue)
│ │ ├── Level (IntValue)
│ │ ├── EXP (IntValue)
│ │ └── PetName (StringValue)
│ └── (Attributes: IsContainer = true, TemplateName = "PetTemplate")
├── Characters
│ ├── CharacterTemplate (Folder)
│ │ ├── HP (IntValue)
│ │ ├── Stamina (IntValue)
│ │ ├── Name (StringValue)
│ │ └── EquippedWeapon (StringValue)
│ └── (Attributes: IsContainer = true, TemplateName = "CharacterTemplate")
--]]
----------------------------------------
-- ⚙️ Dependencies & Setup
----------------------------------------
local DataHandler = {}
DataHandler.__index = DataHandler
local ds = game:GetService("DataStoreService")
local players = game:GetService("Players")
local ss = game:GetService("ServerStorage")
local http = game:GetService("HttpService")
local playerData = ds:GetDataStore("PlayerData")
local dataTemplate = ss:FindFirstChild("DataTemplate")
----------------------------------------
-- 🧩 Configuration
----------------------------------------
local AUTO_SAVE_INTERVAL = 60 -- seconds
local MAX_RETRIES = 5
----------------------------------------
-- 🧰 Core Helper Functions
----------------------------------------
-- 🧬 Clone DataTemplate contents into player
function DataHandler:CreatePlayerData(player)
if not dataTemplate then
warn("[DataHandler] ⚠️ No DataTemplate found in ServerStorage.")
return
end
for _, obj in ipairs(dataTemplate:GetChildren()) do
local clone = obj:Clone()
clone.Parent = player
end
end
-- 🔍 Returns whether a folder is a special container
local function isContainerFolder(folder)
return folder:GetAttribute("IsContainer") == true
end
----------------------------------------
-- 💾 Serialization
----------------------------------------
-- Serializes any ValueBase or Folder recursively
function DataHandler:SerializePlayerData(player)
local data = {}
for _, folder in ipairs(player:GetChildren()) do
if folder:IsA("Folder") then
if isContainerFolder(folder) then
-- 📦 Specialized container folder
local templateName = folder:GetAttribute("TemplateName")
local template = folder:FindFirstChild(templateName)
if not template then
warn("[DataHandler] Missing template for container:", folder.Name)
continue
end
data[folder.Name] = {}
for _, item in ipairs(folder:GetChildren()) do
if item:IsA("Folder") and item.Name ~= template.Name then
local itemData = {}
for _, v in ipairs(item:GetChildren()) do
if v:IsA("ValueBase") then
itemData[v.Name] = v.Value
end
end
data[folder.Name][item.Name] = itemData
end
end
else
-- 🧱 Regular folder
data[folder.Name] = {}
for _, valueObj in ipairs(folder:GetChildren()) do
if valueObj:IsA("ValueBase") then
data[folder.Name][valueObj.Name] = valueObj.Value
end
end
end
elseif folder:IsA("ValueBase") then
-- 🌿 Direct values under player
data[folder.Name] = folder.Value
end
end
return data
end
----------------------------------------
-- 📥 Deserialization
----------------------------------------
function DataHandler:LoadData(player, data)
for folderName, values in pairs(data) do
local folder = player:FindFirstChild(folderName)
if not folder then continue end
if isContainerFolder(folder) then
-- 🔁 Dynamic item reconstruction
local templateName = folder:GetAttribute("TemplateName")
local template = folder:FindFirstChild(templateName)
if not template then
warn("[DataHandler] Missing template for", folderName)
continue
end
for itemName, itemValues in pairs(values) do
local newItem = template:Clone()
newItem.Name = itemName
newItem.Parent = folder
for key, val in pairs(itemValues) do
local valueObj = newItem:FindFirstChild(key)
if valueObj and valueObj:IsA("ValueBase") then
valueObj.Value = val
end
end
end
elseif typeof(values) == "table" then
-- Standard folder
for key, val in pairs(values) do
local valueObj = folder:FindFirstChild(key)
if valueObj and valueObj:IsA("ValueBase") then
valueObj.Value = val
end
end
else
-- Plain value
local valueObj = player:FindFirstChild(folderName)
if valueObj and valueObj:IsA("ValueBase") then
valueObj.Value = values
end
end
end
if not player:FindFirstChild("Loaded") then
local loaded = Instance.new("BoolValue",player)
loaded.Name = "Loaded"
loaded.Value = true
end
end
----------------------------------------
-- 💾 Save / Load Wrappers
----------------------------------------
function DataHandler:SaveData(player)
local success, err
local data = self:SerializePlayerData(player)
for i = 1, MAX_RETRIES do
success, err = pcall(function()
playerData:SetAsync(player.UserId, data)
end)
if success then break end
task.wait(1 + (i * 0.5))
end
if success then
print("[DataHandler] ✅ Saved data for", player.Name)
else
warn("[DataHandler] ❌ Failed to save data for", player.Name, ":", err)
end
end
function DataHandler:LoadOrCreate(player)
local success, data
for i = 1, MAX_RETRIES do
success, data = pcall(function()
return playerData:GetAsync(player.UserId)
end)
if success or i == MAX_RETRIES then break end
task.wait(1 + (i * 0.5))
end
self:CreatePlayerData(player)
if success and data then
self:LoadData(player, data)
print("[DataHandler] ✅ Loaded data for", player.Name)
else
print("[DataHandler] ⚙️ New data created for", player.Name)
end
end
function DataHandler:GetData(player)
return self:SerializePlayerData(player)
end
----------------------------------------
-- 🚀 Initialization
----------------------------------------
function DataHandler:Init()
if not dataTemplate then
warn("[DataHandler] ❌ No DataTemplate found in ServerStorage.")
return
end
players.PlayerAdded:Connect(function(player)
self:LoadOrCreate(player)
end)
for _, player in players:GetPlayers() do
self:LoadOrCreate(player)
end
players.PlayerRemoving:Connect(function(player)
self:SaveData(player)
end)
game:BindToClose(function()
for _, player in ipairs(players:GetPlayers()) do
self:SaveData(player)
end
end)
task.spawn(function()
while true do
task.wait(AUTO_SAVE_INTERVAL)
for _, player in ipairs(players:GetPlayers()) do
self:SaveData(player)
end
end
end)
print("[DataHandler] ✅ Initialized (Dynamic Container Support Active)")
end
return DataHandler
Here is an example:
local players = game:GetService("Players")
local replicatedStorage = game:GetService("ReplicatedStorage")
local simpleStore = require(replicatedStorage:WaitForChild("SimpleStore"))
simpleStore:Init() --initializes the datastore
local generatedIDs = {}
local function GenerateRandomID(Object: Instance)
local Chance = math.random(1,100000)
if generatedIDs[Chance] then
return GenerateRandomID(Object) -- reroll if exists
end
table.insert(generatedIDs,Chance)
Object:SetAttribute("ID",Chance)
return Chance
end
game.Players.PlayerAdded:Connect(function(plr)
local characterFolder = plr:WaitForChild("Characters")
local template = characterFolder.CharacterTemplate --or whatever you name it to be inside dataTemplate
local newCharacter = template:Clone()
GenerateRandomID(newCharacter)
local ID = newCharacter:GetAttribute("ID")
--gives them a random ID (Not neccessary, but good for inventory style systems)
newCharacter.Name = ID
newCharacter.Parent = characterFolder
newCharacter.Shiny.Value = true --value inside to save
end)
You can edit and tweak the values inside of this script all you want, and you don’t have to give me credit if you don’t want to. if
you have any questions, or any new ideas, please ask me. I’m new to creating resources on the dev forum. I used AI-human pairing to create this succesfully, and used the framework from my own games datastore to make it. If you would like to check out my game, it’s linked below. Thank you for viewing my resource, I hope you enjoy Simple Store.
My game Link: ⚔️Medieval Simulator⚔️ (Update 1.3) - Roblox


