How do you rollback/revert datastore (I’m using profilestore

One of my devs accidentally made a 1 million multiplier admin abuse event. And I wanted to make automatic rollback/revert for everyone who got the stats multiplied by the 1 million multiplier.
I hope someone can help me solve this problem. Thank you!

2 Likes

if you didnt have a snapshot before the admin abause, you cant

1 Like

it works using roblox datastore manager feature by comparing it’s version and then revert the version before the player got the stat multiplied by million multiplier. but i wanna know how do i make it automatically?

explain your “automatically”, developers manually revert datastore to a specific snapshot when they know of a dupe glitch, etc

1 Like

like by a script, upon they joined the server their data will be automatically reverted to previous version

we can’t do this manually, there’s lots of players has gotten it

If you’re trying to make it so you can detect or rollback this kind of thing going forward then you should look at data store Metadata. You can set a dictionary of values with each set or update. This could record the user who made the update and what operation was performed. The meta data is stored with each version so you can see why each version was generated.

1 Like

I still don’t understand how it works, i really need an clear explanation

Not too sure what the api difference is between profile store and profile service is however I shall assume they are similar; In ProfileService you can query Player’s data versions with their profile key, min date and a max date, for example if you know when roughy the admin abuse occurred, when you load the player’s profile you can do a quick version query and compare the different data store times before the admin abuse and during the admin abuse, if the stats were multiplied by 1,000,000 then you can simply revert it back to the time before the admin abuse started!


local profileKey = `{UserId}_Data`
	
local maxDate = DateTime.fromUniversalTime(2025,12,02)
local minDate = DateTime.fromUniversalTime(2025,12,01) -- you can also specify hour and mins
	
local versionQuery = ProfileStore:ProfileVersionQuery(profileKey, Enum.SortDirection.Descending,nil,maxDate)

-- do other version query (might need to do  versionQuery:NextAsync())
-- do comparisons here

-- then do this below

profile:ClearGlobalUpdates()

profile:OverwriteAsync()

--and then finally 

Players:GetPlayerByUserId(UserId):Kick("Data Roll Back Success, Please Rejoin")

I assume profile store is very similar.

here is the profile service documentation:

API - ProfileService

1 Like

Assuming the use of the multiplier is obvious then it should be easy to roll it back when you first load the value. After fetching the value check to see if it is greater than one million. If it is adjust it back to the proper value and store the new result.

local success,result = pcall(function()
  local raw,keyinfo = datastore:GetAsync(key)
  assert(typeof(raw) == "table", "Value must be table")
  if raw.stat > 1000000 then
    raw.stat /= 1000000
    datastore:SetAsync(key, raw)
  end
  return true,raw
end)

If a set or update is going to happen automatically then you might not even need to store the value here since it will be handled when the player removes.

The versioning idea is to add metadata each time you add or update the data. This could help you keep track of why the value changed over time. But since a normal game update might have happened since the admin action it could be tricky to use it to back out of an admin change.

  local options = Instance.new("DataStoreOptions")
  options:SetMetaData({action="Action001"})
  datastore:SetAsync(key, value, options)

Then you can fetch the metadata to see the last action that was performed on it.

  local raw, keyinfo = datastore:GetAsync(key)
  local meta = keyinfo:GetMetadata()
  local action = meta.action -- Action001
1 Like

hello there, thank you for helping. but there’s a problem

and this is what i did:

        local minDate = DateTime.fromUniversalTime(2025,12,01, 18, 54)
		local maxDate = DateTime.fromUniversalTime(2025,12,02, 19, 57)

		local versionQuery = PlayerStore:VersionQuery(tostring(player.UserId), Enum.SortDirection.Descending, minDate, maxDate)
		print("ver query")
		print(versionQuery)
		
		local versionInfo = versionQuery:NextAsync()
		print("ver info")
		print(versionInfo)
		if not versionInfo then
			warn("No rollback version found for user:", player.Name)
			return
		end

		print("Rolling back to version:", versionInfo.Version)

i found a way how to do it, but i need to make it check once. how do i compare it?

local max_date = DateTime.fromUniversalTime(2025, 12, 2, 19, 50)

		local query = PlayerStore:VersionQuery(
			tostring(player.UserId), -- The same profile key that gets passed to :LoadProfileAsync()
			Enum.SortDirection.Descending,
			nil,
			max_date
		)

		-- Get the first result in the query:
		local found = query:NextAsync()
		
		if found ~= nil then
			found:SetAsync()
			task.wait(1)
			player:Kick("Data Roll Back Success, Please Rejoin!")
		else
			print(`No version to rollback to`)
		end

Thank you for whoever trying helping me, i found a way to solve it. Thank you so much guys

local max_date = DateTime.fromUniversalTime(2025, 12, 2, 9)

		local query = PlayerStore:VersionQuery(
			tostring(player.UserId), -- The same profile key that gets passed to :LoadProfileAsync()
			Enum.SortDirection.Descending,
			nil,
			max_date
		)

		-- Get the first result in the query:
		local found = query:NextAsync()
		
		if found ~= nil then
			if not profile.Data.HasRolledBack then
				found.Data.HasRolledBack = true
				found:SetAsync()
				print(found.Data)
										
				task.wait(5)
				player:Kick("Data Roll Back Success, Please Rejoin!")
			else
				print("No data to rollback.")
			end
		else
			print(`No version to rollback to`)
		end

no worries, glad I could help !

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.