Skip to content

Retention & Lifetime Cohorts

The RetentionTracker persists player visit history across sessions using DataStoreService. It automatically flags whether a joining user is a first-time player or a returning visitor, and records D1 through D30 retention milestones.


Emitted when a player visits the game for the very first time:

{
"name": "PlayerFirstJoin",
"category": "retention",
"timestamp": 1727790000,
"userId": 12345678,
"sessionId": "b8f67389-1065-4f76-871d-15ec43194511",
"data": {
"accountAgeDays": 140,
"isNewPlayer": true
}
}

Emitted on return visits when an existing player re-enters the game:

{
"name": "PlayerReturnVisit",
"category": "retention",
"timestamp": 1727876400,
"userId": 12345678,
"sessionId": "e2a149b0-91fc-4bc2-9650-20cfd48507c9",
"data": {
"totalVisits": 2,
"daysSinceLastVisit": 1.0,
"daysSinceFirstJoin": 1,
"lifetimePlaytimeHours": 0.5
}
}

Emitted when a returning player crosses a milestone day threshold for the first time:

{
"name": "PlayerRetentionMilestone",
"category": "retention",
"timestamp": 1727876400,
"userId": 12345678,
"sessionId": "e2a149b0-91fc-4bc2-9650-20cfd48507c9",
"data": {
"milestone": "D1",
"daysSinceFirstJoin": 1,
"totalVisits": 2
}
}

Milestones are calculated based on elapsed days since the player’s initial recorded join time:

Milestone Elapsed Days Description
D1 $\ge 1$ day (24 hours) Returned the next day (Day 1 Retention)
D3 $\ge 3$ days (72 hours) Continued playing into the first week
D7 $\ge 7$ days (168 hours) Retained past week 1 (Day 7 Retention)
D14 $\ge 14$ days (336 hours) Retained into week 2
D30 $\ge 30$ days (720 hours) Long-term active player (Day 30 Retention)

Each milestone is claimed once per player and stored in their record (milestonesClaimed).


Retention records are stored under AnalyticsModule_Retention_v1 using key tostring(player.UserId):

type RetentionRecord = {
firstJoinTime: number, -- os.time() of first join
lastJoinTime: number, -- os.time() of most recent visit
totalVisits: number, -- Total visit count
totalPlaytimeSeconds: number, -- Cumulative active session time
milestonesClaimed: { [string]: boolean }, -- Map of claimed milestones: { ["D1"] = true }
}
  • Offline Studio Fallback: If DataStoreService is unavailable or disabled in Studio, the tracker runs in-memory so test sessions don’t error.
  • Server Shutdown: When the server terminates, game:BindToClose flushes all connected players’ playtime back into DataStore.

Method Return Type Description
Analytics:IsNewPlayer(playerOrUserId) boolean Returns true if this is the player’s first visit.
Analytics:GetTotalVisits(playerOrUserId) number Lifetime visit count.
Analytics:GetLifetimePlaytimeSeconds(playerOrUserId) number Total playtime in seconds across all visits.

Example: Welcoming New vs. Returning Players

Section titled “Example: Welcoming New vs. Returning Players”
local Players = game:GetService("Players")
Players.PlayerAdded:Connect(function(player)
-- Allow DataStore record to load
task.wait(1)
if Analytics:IsNewPlayer(player) then
print(player.Name .. " is a new player! Showing onboarding...")
TutorialService:BeginFirstTimeOnboarding(player)
else
local visits = Analytics:GetTotalVisits(player)
local hours = Analytics:GetLifetimePlaytimeSeconds(player) / 3600
print(string.format("Welcome back %s (Visit #%d, %.1f hrs played)", player.Name, visits, hours))
RewardService:GrantReturnBonus(player, visits)
end
end)