D1, D7 and D30 retention: why today's chart is empty, and what to do
Retention measures who comes back after the first visit, at 1, 7 and 30 days. The axis is the date of the FIRST session, not of the return — and that is why today's D30 only exists a month from now.
Produced with AI from the sources cited below, and checked by automated verification against those same sources. No person read this text before publication. Editorial Policy

You open the retention dashboard for the first time, look at the D30 chart, and the right-hand end is empty. The natural reaction is to think something is wrong — with the game, with the instrumentation, with Roblox.
Nothing is. The emptiness is the thing working, and understanding why is what unlocks reading the rest of the dashboard. This guide explains what retention measures, how the dashboard organises the data, and what the documentation recommends for moving the number.
The problem
The definition is short: retention measures how many users come back to the game after their first visit, at 1, 7 and 30 days.
What confuses people is not the definition, it is the axis. The horizontal axis of the cohorts is the date of the new user's first session — not the date they came back. The documentation explains why: it gives a more accurate view of how a change made on one day affects that group's retention over time.
Translated into everyday terms: when you look at a point on the D7 chart, that point is not about who played on that date. It is about who debuted on that date and came back a week later. The point belongs to the debut day, and that is why it says something about what your game was on that day.
With the three charts side by side, the same date tells three stories about the same group: who debuted there and came back the next day, who came back after a week, and who came back after a month.
And then the emptiness explains itself. The data for the most recent dates on the D7 and D30 charts can be empty simply because the time has not passed yet. A group's D7 appears seven days after its debut; the D30 appears thirty days after. The right-hand end of the D30 chart will always be empty — it is the month that has not happened yet.
That has an operational consequence worth committing to memory: you cannot measure the D30 of a change you made yesterday. If your decision depends on D30, it depends on waiting.
Step by step
1. Get to the dashboard. In Creations, choose the game, and in the Analytics menu on the left choose Retention. For a group game, you need analytics permission in the group — which is usually the reason somebody on the team cannot see anything.
2. Read the three charts as one. Find a date and read its three lines. A good D1 with a bad D7 is a different problem from a bad D1 — the first is a lack of reason to come back during the week, the second is a lack of reason to come back the next day.
3. Scroll down to the cohort table. The new user cohort table sits at the bottom of the retention dashboard, and it is the most useful and least looked-at part.
There are two views. The daily cohort shows how many new users were retained over the first ten days. The weekly cohort follows ten weeks, grouping by week of entry, Monday to Sunday.
The difference in use is direct: the daily one shows the shape of the drop right after the debut; the weekly one shows whether the game holds people for more than two months.
4. Use the cohorts to measure changes. That is what they exist for: tracking the impact of a big update, an event or a campaign. The question they answer, and that no aggregate metric answers, is whether whoever arrived during the event stayed longer than whoever was already there.
5. Cross it with money. Each cohort brings funnel metrics, among them accumulated seven-day playtime per user, the accumulated seven-day payer conversion rate, and accumulated thirty-day revenue per user.
This is where the analysis stops being academic. An event that brought in a lot of people who do not monetise and an event that brought in fewer people who monetise well look identical in the player count, and completely different in the cohort table.
The artifact
The module below instruments the first session funnel, which is the part the dashboard does not do for you. The dashboard says D1 is low; it does not say at which step of your loop people gave up.
It follows the official recipe in order: list the loop steps, track the completion rate of each one, track also what went wrong, and point at the biggest drop.
Two design details are worth explaining. The first: it only measures whoever is in their first session — anyone who has played before walks the funnel fast and would dirty the measure. The second: marking a step is idempotent and refuses out-of-order jumps, because a skipped step is almost always a call bug, not player behaviour, and a funnel with dirty data is worse than none.
Common mistakes
The most common reading mistake is panicking at the dive after a spike of players.
The pattern is this: you run a campaign, or land in a featured sort, and a lot of new users arrive at once. The next day, the retention chart dives at that date — D1, D7 and D30 together.
The documentation says what that is, in as many words: a typical and temporary result after a large influx of new users. It makes sense. A player who arrived out of curiosity, from an advert or by accident is, on average, less likely to come back than one who arrived looking for your game specifically. The denominator grows faster than the numerator, and the rate falls without anything in the game having got worse.
The expensive mistake is reacting to that dive by changing the game. You fix something that was not broken, and on top of that you attribute the curve's natural recovery to the fix — learning a wrong lesson that will cost you next time.
The correct reading is comparing cohorts with cohorts: the campaign group against the previous week's group, in the same table. If the campaign group retains worse, the question is about the traffic source, not about the game.
The second reading mistake is quieter: comparing D1 across dates that are not comparable. Weekends, school holidays and event days bring a different audience from a Tuesday afternoon, and the curve reflects that. Comparing Tuesday with Tuesday and Saturday with Saturday answers questions; comparing yesterday with today answers almost nothing.
And there is a third, about the window: drawing a conclusion from a cohort that is still forming. A D7 point that appeared today is about people who debuted a week ago — it is complete. A D7 point with no data is not a bad number, it is the absence of a number, and treating it as zero pulls any average down. If you export the data to a spreadsheet, that distinction has to survive the export, because an empty cell and a cell with zero look far too alike.
Next step
To move D1, the documentation points to three fronts: the core loop, the first-time experience and performance.
The core loop is the set of actions the user repeats to progress within a single session — in a pet adoption game, adopting, training and levelling up; in a tycoon, running the business, balancing resources and competing with others. The work on it has three steps: identify the steps and make them clear and fun, balance the difficulty to avoid frustration and boredom, and reward completion with feedback, currency, items and achievements.
The first-time experience is how a new user lives that loop for the first time, and the recommendations are four: a brief tutorial or contextual information such as on-screen tips; avoid a long tutorial, because the goal is reaching the fun fast; deliver a moment of joy when the loop is completed for the first time; and show a preview of the progress possible if it is repeated.
And there is a numeric target worth writing on the wall: five minutes or less for the player to reach the fun after joining. It is a test you can run today, with a stopwatch, with no dashboard at all — join your own game as if for the first time and time how long it takes. If it goes past five minutes, you already know where to work before the next D1 appears.
Sources
- Retention | Documentation - Roblox Creator Hub — checked on
Ready-to-use artifact
-- ServerScriptService: the FIRST session funnel.
--
-- The official recommendation for improving D1 has four steps, and the
-- last three depend on instrumentation: list the loop steps, track the
-- completion rate of each one, track what goes wrong too, and find the
-- biggest drop.
--
-- This module does the measuring part. It does NOT replace the analytics
-- dashboard: it answers the question the dashboard does not answer, which
-- is "at which STEP of my loop do people give up".
local Players = game:GetService("Players")
local Funnel = {}
-- The steps of YOUR loop, in order. Swap in your own.
local STEPS = { "adopted", "trained", "levelled_up" }
local indexOf = {}
for i, name in STEPS do
indexOf[name] = i
end
-- Count for this server's session. To make it hold across servers, send
-- these numbers wherever you already send telemetry.
local reached = {} -- step -> how many got there
local negatives = {} -- label -> how many times
local joinedAt = {}
local function fresh(player)
-- only instrument whoever is in their FIRST session; anyone who has
-- played before walks the funnel fast and would dirty the measure.
if player:GetAttribute("HasPlayed") then
return
end
joinedAt[player] = os.clock()
player:SetAttribute("FunnelStep", 0)
end
-- Call this when the player COMPLETES a step. Idempotent: repeating the
-- same step does not count twice, and skipping a step is not accepted.
function Funnel.mark(player, name)
local target = indexOf[name]
if not target then
warn("unknown step in the funnel: " .. tostring(name))
return
end
local current = player:GetAttribute("FunnelStep")
if current == nil or current >= target then
return
end
if target ~= current + 1 then
-- a skipped step is almost always a call bug, not the player
warn("funnel out of order: " .. name .. " after " .. current)
return
end
player:SetAttribute("FunnelStep", target)
reached[name] = (reached[name] or 0) + 1
if target == #STEPS then
local seconds = os.clock() - (joinedAt[player] or os.clock())
print(string.format("loop completed in %.0f s", seconds))
end
end
-- Call this on whatever goes WRONG: died, lost the fight, closed the menu
-- without buying. The official recommendation is to track both sides.
function Funnel.negative(label)
negatives[label] = (negatives[label] or 0) + 1
end
-- Where the biggest drop is, as a percentage of those who reached the previous step.
function Funnel.report()
local lines = {}
local previous = nil
for _, name in STEPS do
local n = reached[name] or 0
local rate = previous and previous > 0 and (n / previous * 100) or nil
table.insert(lines, string.format(
"%-18s %5d %s", name, n,
rate and string.format("%.0f%% of the previous step", rate) or "-"
))
previous = n
end
for label, n in negatives do
table.insert(lines, string.format("%-18s %5d (negative)", label, n))
end
return table.concat(lines, "\n")
end
Players.PlayerAdded:Connect(fresh)
Players.PlayerRemoving:Connect(function(player)
joinedAt[player] = nil
end)
return Funnel

