Skip to content

Commit 1e7ec74

Browse files
authored
docs: add Roblox SDK (#17343)
1 parent 4f58f88 commit 1e7ec74

10 files changed

Lines changed: 658 additions & 1 deletion

File tree

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
---
2+
title: Roblox error tracking installation
3+
platformLogo: roblox
4+
showStepsToc: true
5+
---
6+
7+
import { Steps, Step } from 'components/Docs/Steps'
8+
import { CalloutBox } from 'components/Docs/CalloutBox'
9+
10+
<Steps>
11+
12+
<Step title="Install the Roblox SDK" badge="required">
13+
14+
PostHog is available for Roblox through [Wally](https://wally.run) or as a model file you import in Studio.
15+
16+
Via Wally, add the dependency to your `wally.toml`, run `wally install`, and map the package into `ReplicatedStorage`:
17+
18+
```toml
19+
[dependencies]
20+
PostHog = "posthog/posthog-roblox@0.1.0"
21+
```
22+
23+
You can also download the latest `posthog-roblox.rbxm` from the [releases page](https://github.com/PostHog/posthog-roblox/releases) and insert it into `ReplicatedStorage` in Studio. Either way you should end up with `ReplicatedStorage > PostHog`.
24+
25+
Make sure HTTP requests are enabled for your experience: **Game Settings > Security > Allow HTTP Requests**.
26+
27+
</Step>
28+
29+
<Step title="Configure PostHog" badge="required">
30+
31+
Initialize PostHog once from a server `Script` (for example in `ServerScriptService`), keeping error capture enabled:
32+
33+
```lua
34+
local ReplicatedStorage = game:GetService("ReplicatedStorage")
35+
local PostHog = require(ReplicatedStorage:WaitForChild("PostHog"))
36+
37+
PostHog:Init({
38+
apiKey = "<ph_project_token>",
39+
host = "<ph_client_api_host>",
40+
captureErrors = true,
41+
errorDebounceSeconds = 1,
42+
})
43+
```
44+
45+
You can find your project token and instance address in the [project settings](https://app.posthog.com/project/settings) page in PostHog.
46+
47+
</Step>
48+
49+
<Step title="Capture exceptions" badge="required">
50+
51+
The Roblox SDK captures exceptions automatically by default. On the server it listens to `ScriptContext.Error`; requiring the module from a `LocalScript` captures unhandled client errors and relays them to the server.
52+
53+
Captured events include the error message, the stack trace when available, and SDK metadata.
54+
55+
For handled errors, call `CaptureException` manually with a subject, message, and optional trace and properties:
56+
57+
```lua
58+
local ok, err = pcall(function()
59+
saveGame(player)
60+
end)
61+
62+
if not ok then
63+
PostHog:CaptureException(player, err, debug.traceback(), {
64+
flow = "save_game",
65+
})
66+
end
67+
```
68+
69+
<CalloutBox icon="IconInfo" title="Identified users" type="fyi">
70+
71+
Server events are already attributed to the player you pass as the subject (by their `UserId`), so exceptions are linked to known users in PostHog automatically.
72+
73+
</CalloutBox>
74+
75+
</Step>
76+
77+
<Step title="Configure error tracking options" badge="optional">
78+
79+
| Option | Type | Default | Description |
80+
| --- | --- | --- | --- |
81+
| `captureErrors` | boolean | `true` | Enables automatic exception capture. |
82+
| `errorDebounceSeconds` | number | `1` | Minimum seconds between automatic server error captures. |
83+
84+
To disable automatic exception capture:
85+
86+
```lua
87+
PostHog:Init({
88+
apiKey = "<ph_project_token>",
89+
captureErrors = false,
90+
})
91+
```
92+
93+
</Step>
94+
95+
<Step checkpoint title="Verify error tracking" badge="recommended">
96+
97+
Trigger a test exception and confirm it appears in the [Error tracking](https://app.posthog.com/error_tracking) tab.
98+
99+
```lua
100+
PostHog:CaptureException(nil, "Test exception from Roblox", debug.traceback())
101+
PostHog:Flush()
102+
```
103+
104+
To test automatic capture, raise an unhandled error from a server `Script`:
105+
106+
```lua
107+
error("Automatic test exception from Roblox")
108+
```
109+
110+
</Step>
111+
112+
</Steps>

contents/docs/error-tracking/references.mdx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ If you're looking for detailed reference documentation for the PostHog SDKs and
1313
- [Python SDK](/docs/references/posthog-python?filter=error-tracking)
1414
- [.NET SDK](/docs/libraries/dotnet#error-tracking)
1515
- [Unity SDK](/docs/libraries/unity#error-tracking)
16+
- [Roblox SDK](/docs/libraries/roblox#error-tracking)
1617

1718
## API references
1819

contents/docs/getting-started/_snippets/install.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { WebsiteJSHtmlSnippet } from '../../product-analytics/installation/_snip
1111
<Tab.List>
1212
<Tab>AI wizard</Tab>
1313
<Tab>JS snippet</Tab>
14-
<Tab count="15">Libraries</Tab>
14+
<Tab count="16">Libraries</Tab>
1515
<Tab count="22">Framework guides</Tab>
1616
<Tab>API</Tab>
1717
</Tab.List>
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
PostHog is available for Roblox through [Wally](https://wally.run) (the Roblox package manager), or as a model file you import in Studio. The SDK is server-authoritative: the server owns all batching and HTTP, and each player is a PostHog user keyed by their Roblox `UserId`.
2+
3+
### Requirements
4+
5+
- HTTP requests enabled for your experience: **Game Settings > Security > Allow HTTP Requests**. Only the Roblox server can make outbound HTTP requests, and only when this is on.
6+
- A PostHog project API key (starts with `phc_`).
7+
8+
### Via Wally (recommended)
9+
10+
Add the dependency to your `wally.toml`:
11+
12+
```toml
13+
[dependencies]
14+
PostHog = "posthog/posthog-roblox@0.1.0"
15+
```
16+
17+
Run `wally install`, then map the installed package into `ReplicatedStorage` in your [Rojo](https://rojo.space) project file so it replicates to clients:
18+
19+
```json
20+
"ReplicatedStorage": {
21+
"$className": "ReplicatedStorage",
22+
"PostHog": { "$path": "Packages/PostHog" }
23+
}
24+
```
25+
26+
### Via model file
27+
28+
Download the latest `posthog-roblox.rbxm` from the [releases page](https://github.com/PostHog/posthog-roblox/releases). In Studio, right-click `ReplicatedStorage`, choose **Insert from File**, and select the file. Make sure the inserted instance is named `PostHog`.
29+
30+
Either way, you should end up with `ReplicatedStorage > PostHog`, which both server and client code require.
31+
32+
### Initialize on the server
33+
34+
Initialize PostHog once from a server `Script` (for example in `ServerScriptService`):
35+
36+
```lua
37+
local ReplicatedStorage = game:GetService("ReplicatedStorage")
38+
local PostHog = require(ReplicatedStorage:WaitForChild("PostHog"))
39+
40+
PostHog:Init({
41+
apiKey = "<ph_project_token>",
42+
host = "<ph_client_api_host>", -- usually https://us.i.posthog.com or https://eu.i.posthog.com
43+
})
44+
```
45+
46+
> **Note:** `Init` must run on the server. Calling it from a `LocalScript` is ignored. Requiring the same module from a `LocalScript` gives you a thin client relay instead (see [Capturing events](#capturing-events)).
47+
48+
### Configuration options
49+
50+
Pass any of these to `PostHog:Init`. Only `apiKey` is required; everything else has a sensible default.
51+
52+
```lua
53+
PostHog:Init({
54+
-- Required
55+
apiKey = "<ph_project_token>",
56+
57+
-- Connection
58+
host = "https://us.i.posthog.com",
59+
60+
-- Event queue
61+
flushAt = 20, -- Flush once this many events are queued (default: 20)
62+
flushIntervalSeconds = 30, -- Flush at least this often (default: 30)
63+
maxQueueSize = 1000, -- Drop the oldest events past this size (default: 1000)
64+
maxBatchSize = 50, -- Maximum events per HTTP request (default: 50)
65+
66+
-- Feature flags
67+
preloadFeatureFlags = true, -- Fetch a player's flags when they join (default: true)
68+
sendFeatureFlagEvents = true, -- Emit $feature_flag_called when a flag is read (default: true)
69+
sendDefaultPersonPropertiesForFlags = true,
70+
71+
-- Autocapture and error tracking
72+
captureLifecycleEvents = true, -- player_joined/left/idle, server_started/shutdown (default: true)
73+
captureErrors = true, -- Capture unhandled errors as $exception events (default: true)
74+
errorDebounceSeconds = 1, -- Minimum gap between auto-captured server errors (default: 1)
75+
76+
-- Identity
77+
personProfiles = "identified_only", -- "identified_only" | "always" | "never"
78+
serverDistinctId = "server", -- distinct_id used for server-scoped (nil subject) events
79+
80+
-- Client relay
81+
enableClientRelay = true, -- Create the RemoteEvent that lets clients relay events (default: true)
82+
clientRateLimitPerSecond = 20, -- Per-player rate limit for relayed messages (default: 20)
83+
maxClientPropertyCount = 100, -- Maximum properties accepted from one client message (default: 100)
84+
85+
-- Diagnostics
86+
logLevel = "warn", -- "debug" | "info" | "warn" | "error" | "none"
87+
})
88+
```
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
Feature flags in the Roblox SDK are evaluated **per player**, so each flag call takes the player (or another subject) as its first argument. When `preloadFeatureFlags` is enabled (the default), a player's flags are fetched automatically when they join, so they're ready to read for the rest of the session.
2+
3+
### Boolean feature flags
4+
5+
```lua
6+
if PostHog:IsFeatureEnabled(player, "flag-key") then
7+
-- Do something differently for this player
8+
end
9+
```
10+
11+
`IsFeatureEnabled` accepts an optional default that's returned when the flag hasn't loaded yet:
12+
13+
```lua
14+
if PostHog:IsFeatureEnabled(player, "flag-key", false) then
15+
-- ...
16+
end
17+
```
18+
19+
### Multivariate feature flags
20+
21+
`GetFeatureFlag` returns a table with the flag's `value`, `isEnabled`, `variant`, and `payload`:
22+
23+
```lua
24+
local flag = PostHog:GetFeatureFlag(player, "flag-key")
25+
if flag.isEnabled then
26+
if flag.variant == "variant-key" then -- Replace with your variant key
27+
-- Do something for this variant
28+
end
29+
end
30+
```
31+
32+
### Feature flag payloads
33+
34+
Read the JSON payload attached to a flag with `GetFeatureFlagPayload`:
35+
36+
```lua
37+
local payload = PostHog:GetFeatureFlagPayload(player, "shop-config")
38+
if payload then
39+
print(payload.theme, payload.maxItems)
40+
end
41+
```
42+
43+
The payload is also available on the flag object returned by `GetFeatureFlag`:
44+
45+
```lua
46+
local flag = PostHog:GetFeatureFlag(player, "shop-config")
47+
local theme = flag.payload and flag.payload.theme
48+
```
49+
50+
### Ensuring flags are loaded before use
51+
52+
When a player joins, the SDK fetches their flags in the background (unless you set `preloadFeatureFlags = false`). For most of a session the flags are available immediately, but the very first read right after a player joins can race the network request.
53+
54+
To force a fresh fetch and wait for it, call `ReloadFeatureFlags`. It **yields** until the request completes and returns whether it succeeded:
55+
56+
```lua
57+
local ok = PostHog:ReloadFeatureFlags(player)
58+
if ok then
59+
local enabled = PostHog:IsFeatureEnabled(player, "flag-key")
60+
end
61+
```
62+
63+
### Overriding properties for flag evaluation
64+
65+
Set the person or group properties used to evaluate a player's flags. By default this reloads their flags; pass `false` as the final argument to skip the reload:
66+
67+
```lua
68+
-- Person properties for targeting
69+
PostHog:SetPersonPropertiesForFlags(player, {
70+
level = 12,
71+
is_vip = true,
72+
})
73+
74+
-- Group properties for targeting
75+
PostHog:SetGroupPropertiesForFlags(player, "guild", {
76+
member_count = 42,
77+
})
78+
```
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import SettingProperties from "./setting-properties-text.mdx"
2+
import Intro from "./intro.mdx"
3+
4+
<Intro />
5+
6+
On the server, every capture call takes a **subject** as its first argument so you can attribute the event to a specific player. Pass a `Player` and it resolves to their `UserId`:
7+
8+
```lua
9+
local Players = game:GetService("Players")
10+
11+
Players.PlayerAdded:Connect(function(player)
12+
PostHog:Capture(player, "game_joined")
13+
end)
14+
```
15+
16+
> **Tip:** We recommend naming events in an `[object] [verb]` format, where `[object]` is the entity the behavior relates to, and `[verb]` is the behavior itself. For example, `level completed`, `item purchased`, or `quest started`.
17+
18+
The subject can be a `Player`, a numeric `UserId`, a string distinct ID, or `nil` for a server-scoped event that isn't attributed to any player:
19+
20+
```lua
21+
PostHog:Capture(player, "level_completed") -- a Player
22+
PostHog:Capture(123456, "level_completed") -- a UserId
23+
PostHog:Capture(nil, "shop_restocked") -- server-scoped, no person profile
24+
```
25+
26+
<SettingProperties />
27+
28+
```lua
29+
PostHog:Capture(player, "level_completed", {
30+
level = 5,
31+
duration_seconds = 92,
32+
used_powerup = true,
33+
})
34+
```
35+
36+
### Capturing from the client
37+
38+
The same `PostHog` module works in a `LocalScript`, where it acts as a thin relay: calls are sent to the server over a `RemoteEvent` and attributed to the firing player. There's no API key on the client, and no subject argument:
39+
40+
```lua
41+
local ReplicatedStorage = game:GetService("ReplicatedStorage")
42+
local PostHog = require(ReplicatedStorage:WaitForChild("PostHog"))
43+
44+
PostHog:Capture("button_clicked", { button = "play" })
45+
```
46+
47+
The server validates, rate-limits, and attributes every relayed event, so a client can't spoof another player or send reserved (`$`-prefixed) event names.
48+
49+
### Capturing screen views
50+
51+
Track which screens or menus a player views:
52+
53+
```lua
54+
PostHog:Screen(player, "Main Menu")
55+
56+
-- With additional properties
57+
PostHog:Screen(player, "Level Select", {
58+
unlocked_levels = 5,
59+
})
60+
61+
-- From a LocalScript (relayed, no subject)
62+
PostHog:Screen("Shop")
63+
```

0 commit comments

Comments
 (0)