Roblox checkpoint script (obby stages that save)

Numbered checkpoints, a Stage counter on the leaderboard and respawning at the last one reached — saved between visits.

ScriptUpdated November 2, 2026

Where to put it

Script — ServerScriptService > Checkpoints (Script). It runs on the server, so players can't change what it does.

  1. Put flat anchored parts named 1, 2, 3… in a Folder named Checkpoints in Workspace. Keep a SpawnLocation next to checkpoint 1.
  2. Publish the place and turn on Studio API access if you want saving to work in Studio.

The script

ServerScriptService > Checkpoints (Script)
local Players = game:GetService("Players")
local DataStoreService = game:GetService("DataStoreService")

local store = DataStoreService:GetDataStore("ObbyStage_v1")
local checkpoints = workspace:WaitForChild("Checkpoints")

local function moveToCheckpoint(player, character)
	local checkpoint = checkpoints:FindFirstChild(tostring(player.leaderstats.Stage.Value))
	if checkpoint then
		character:WaitForChild("HumanoidRootPart")
		task.wait()
		character:PivotTo(checkpoint.CFrame + Vector3.new(0, 4, 0))
	end
end

Players.PlayerAdded:Connect(function(player)
	local leaderstats = Instance.new("Folder")
	leaderstats.Name = "leaderstats"
	leaderstats.Parent = player
	local stage = Instance.new("IntValue")
	stage.Name = "Stage"
	stage.Value = 1
	stage.Parent = leaderstats

	local ok, saved = pcall(function()
		return store:GetAsync("player_" .. player.UserId)
	end)
	if ok then
		stage.Value = saved or 1
		player:SetAttribute("DataLoaded", true)
	end

	player.CharacterAdded:Connect(function(character)
		moveToCheckpoint(player, character)
	end)
	if player.Character then
		moveToCheckpoint(player, player.Character)
	end
end)

Players.PlayerRemoving:Connect(function(player)
	if not player:GetAttribute("DataLoaded") then return end
	pcall(function()
		store:SetAsync("player_" .. player.UserId, player.leaderstats.Stage.Value)
	end)
end)

for _, checkpoint in checkpoints:GetChildren() do
	local number = tonumber(checkpoint.Name)
	checkpoint.Touched:Connect(function(hit)
		local player = Players:GetPlayerFromCharacter(hit.Parent)
		if player and number and number == player.leaderstats.Stage.Value + 1 then
			player.leaderstats.Stage.Value = number
		end
	end)
end

How it works

  • Only the next checkpoint counts, so players can't skip ahead by touching a later one.
  • After every respawn the character is moved to the checkpoint matching its Stage.
  • Stage is saved on leave and loaded on join; it isn't saved if loading failed, so progress is never wiped.

Variations

Skip stage product

Sell a developer product that adds 1 to Stage and calls player:LoadCharacter(). Grant it in MarketplaceService.ProcessReceipt and return PurchaseGranted.

Checkpoint feedback

On a new stage, play a sound and briefly turn the checkpoint green so players know it saved.

Common mistakes

  • Checkpoints named “Checkpoint1” instead of 1 — tonumber returns nil and nothing saves.
  • Two parts with the same number, or gaps in numbering: the stage can't advance past the gap.
  • Moving the character in the same frame it spawns — wait for HumanoidRootPart and one frame, as above.

FAQ

How do I make checkpoints save in a Roblox obby?

Save the Stage value with DataStoreService when the player leaves and load it when they join, as the script on this page does. Data stores work only in published places.

Can I use SpawnLocations as checkpoints?

You can, using Player.RespawnLocation, but many enabled SpawnLocations make new players spawn at random ones. Plain parts plus moving the character, as here, avoids that.

Keep reading