Most game analytics dashboards are decorative. They look impressive in a Loom and sit unused on a tab nobody opens by week three. The dashboards that survive are the ones that answer specific recurring questions, fast, with metrics the team has already agreed are decision-grade. The RoLearn SDK's Dashboard Builder and Segments DSL are the tools you use to build those. This guide walks through the tile catalog, the segment expression language, and the three layout patterns we see survive the six-month test in production studios.
The model in one paragraph
A custom dashboard is a layout of tiles. Each tile binds to a metric or aggregate computed from your SDK events. Tiles can be filtered by a segment — a named subset of your players defined by a small DSL expression. A dashboard can have one default segment (everyone, EU only, whales, etc.) that applies to all its tiles, or per-tile segments for comparison views. Layouts and segments live in the database and are team-scoped — every member of your team sees the same dashboards, edits stay in sync.
The tile catalog
The tile catalog (Dashboard Builder → Add Tile) groups tiles by the question they answer. The five families that cover most common dashboards:
Totals card
A compact metric card displaying a single number with optional period-over-period delta and a sparkline. Best for headline KPIs your team agrees on: DAU, daily revenue, ARPDAU, transactions per day, active sessions. Bind one tile per metric; don't pack multiple unrelated numbers into one card — readers scan one number per card faster.
Leaderboard
A ranked list of entities (countries, levels, characters, SKUs, A/B variants) sorted by a metric. The default binding is countries ranked by DAU with a trend sparkline column. Best for "what's the top N right now" questions. Limit rows to 10 unless you have a specific reason for 20+; long leaderboards scroll past the fold and lose their value.
Funnel / FunnelCompare
A stepwise conversion view of an ordered sequence of events. Two variants: a single-funnel view (one segment, one funnel), and a comparison view (same funnel, two or more segments side-by-side). The comparison view is the one teams use most — "did the new tutorial improve the spawn → first_purchase funnel for the variant B segment?" Picking the right events as funnel steps is the entire game; see the custom event patterns guide.
Donut / ItemsMatrix
Donut for share-of-total (top 5 countries as % of DAU, top 5 SKUs as % of revenue). ItemsMatrix for a grid of small donuts compared across a dimension (top 3 SKUs by country, top 5 characters by region). Use sparingly — donuts work for share-of-total but mislead when there are more than 6-7 categories. If everything else fails, fall back to a sorted leaderboard.
Recent events feed
A scrolling list of the most recent N events from your stream. Bound by event type filter (e.g. only purchases, only crashes). Best as a debugging or live-ops surface, not a daily dashboard — it produces noise more than signal when you scale past a few hundred events per day. Keep it on a "operations" tab, not the main team dashboard.
Segments: the DSL you actually need to learn
A segment is a named subset of your players defined by a boolean expression. The DSL is intentionally small — two condition shapes (event_count and player_attr) composed with AND / OR. The grammar:
{
"op": "AND" | "OR",
"conditions": [
{
"field": "event_count",
"event_type": "purchase" | "level_completed" | ...,
"op": ">=" | ">" | "=" | "<=" | "<" | "!=",
"value": 3,
"window_days": 30,
"payload_key": "currency", // optional
"payload_value": "USD" // optional
},
{
"field": "player_attr",
"attr": "country" | "membership" | "device" | "os" | ...,
"op": "=" | "!=" | "in" | "exists",
"value": "US"
}
]
}A handful of realistic example segments your team will actually use:
"US payers" — players who purchased ≥3 times in 30 days from the US
{
"op": "AND",
"conditions": [
{ "field": "event_count", "event_type": "purchase",
"op": ">=", "value": 3, "window_days": 30 },
{ "field": "player_attr", "attr": "country",
"op": "=", "value": "US" }
]
}"Mobile-only EU active" — EU players on mobile devices with ≥5 sessions in 7d
{
"op": "AND",
"conditions": [
{ "field": "event_count", "event_type": "session_start",
"op": ">=", "value": 5, "window_days": 7 },
{ "field": "player_attr", "attr": "country",
"op": "in",
"value": ["DE","FR","ES","IT","NL","PL","SE"] },
{ "field": "player_attr", "attr": "device",
"op": "=", "value": "mobile" }
]
}"Lapsed whales" — players with ≥5 purchases historically but no session in last 14d
{
"op": "AND",
"conditions": [
{ "field": "event_count", "event_type": "purchase",
"op": ">=", "value": 5, "window_days": 180 },
{ "field": "event_count", "event_type": "session_start",
"op": "=", "value": 0, "window_days": 14 }
]
}"A/B test cohort, variant B"
{
"op": "AND",
"conditions": [
{ "field": "event_count", "event_type": "ab_assignment",
"op": ">=", "value": 1, "window_days": 30,
"payload_key": "experiment", "payload_value": "lobby_layout_v3" },
{ "field": "event_count", "event_type": "ab_assignment",
"op": ">=", "value": 1, "window_days": 30,
"payload_key": "variant", "payload_value": "B" }
]
}Segment materialization: what happens when you save
Saving a segment definition does not immediately compute its membership — that happens nightly at 03:00 UTC by the materializer job, which evaluates the DSL against your event stream and writes the resulting player_id set into the membership table. For ad-hoc evaluation, the Segments page has a "Materialize now" button that runs the job synchronously for that one segment (allow a minute or two for segments with many conditions).
Three things to know about materialization:
- Membership is point-in-time. A "US payer with ≥3 purchases in 30 days" segment evaluated today is different from the same segment evaluated yesterday, because the trailing 30-day window slid. That's the right behavior; don't try to freeze a segment's membership unless you have a specific analytical reason.
- Errors in DSL evaluation (a referenced event type that doesn't exist in your stream, a malformed payload filter) are persisted to the segment's
last_errorfield and shown in the Segments UI. They do not crash the materializer; other segments continue evaluating. - Segment membership counts are visible on the Segments index page, so you can sanity-check sizes before binding a dashboard to a segment that turned out to be empty.
Three layout patterns that survive the six-month test
Pattern 1 — the "team standup" dashboard
One dashboard that the whole team looks at for ten minutes every Monday. Layout:
- Top row: 4 Totals cards — DAU, daily revenue, 7-day retention, 7-day new-user count. Each with a 7-day sparkline.
- Middle row: 2 Leaderboards — top 10 countries by DAU, top 10 SKUs by revenue.
- Bottom row: 1 Funnel — your game's primary monetization funnel (session_start → shop_opened → item_inspected → purchase).
Bound to default segment "All players". Read once a week, no per-tile filtering, no segment switching. The point is consistency — the team agrees on these numbers and sees them in the same place every Monday.
Pattern 2 — the "experiment review" dashboard
One dashboard per A/B test in flight. Layout:
- Top: 2 Totals cards — variant A retention vs variant B retention.
- Middle: 1 FunnelCompare — variant A vs variant B on the primary conversion funnel.
- Bottom: 1 ItemsMatrix — top 5 SKUs by variant, side by side.
Each tile bound to a per-variant segment (the "A/B test cohort" examples above). The whole dashboard is throwaway — delete it when the experiment concludes. Resist the urge to keep it around "for reference"; outdated experiment dashboards are dashboard-debt you'll never pay off.
Pattern 3 — the "live-ops control room" dashboard
One dashboard that goes on a wall-mounted screen during events, launches, or crisis periods. Layout:
- Top-left: Totals card — current CCU, refreshing every 60s.
- Top-right: Totals card — events per minute, refreshing every 60s.
- Middle: Recent events feed filtered to
crash+error. - Bottom: Leaderboard — top 10 errors by signature_hash count in the last hour.
Existence-of-dashboard is the entire point. Even if nobody looks at it most of the time, having it always available during a launch is the difference between catching a backend outage in 90 seconds vs 30 minutes.
Anti-patterns we see most often
- Too many tiles per dashboard. Past about 12 tiles, no one can hold the dashboard's information in their head, and the dashboard loses authority. Two dashboards of 6 tiles each beats one dashboard of 12.
- Tiles whose value never changes. Total lifetime users counter has been "47.3 million" for the past four weeks. Replace with a 7-day-window version that actually moves.
- Segments that are too narrow to be statistically meaningful. If your segment materializes to 23 players, retention curves on it are noise. Define segments with at least a few hundred members or accept the noise explicitly.
- Comparison segments that overlap. An A/B test segmented by "variant A" and "variant B" should be disjoint by construction. If players are bucketed into both (e.g. due to a re-roll bug), the comparison view is uninterpretable. Verify segment disjointness in the Segments index before trusting comparison dashboards.
- Dashboards with no owner. A team dashboard with no named owner becomes a graveyard within months. Pin each dashboard to a named team member who is responsible for keeping it useful.
Where to go next
- Custom Event Tracking Patterns — dashboards are only as good as the events feeding them.
- SDK integration reference — full event schema + supported event types.
- Roblox SDK Quickstart — if you don't have events flowing yet, start here.
- GDPR Compliance with the RoLearn SDK — what happens to dashboard tiles when a player requests data deletion.
