Every Playloop SDK ships with a built-in crash trap. Uncaught exceptions get captured locally, then shipped on the next session start as a structured report. Stacks group by signature, so a single bug shows once with a count instead of forty-seven duplicate rows.
When you create a PlayloopClient (TypeScript, Python, Unity, Unreal, Godot), the SDK installs a platform-appropriate crash trap and drains any crashes persisted from a prior run. Browser SDKs hook window.onerror and unhandledrejection. Node hooks uncaughtException. Unity hooks Application.logMessageReceived. Unreal hooks FCoreDelegates::OnHandleSystemError for OS-level crashes. Python hooks sys.excepthook and threading. Godot exposes Playloop.report_crash() for the engine error-handling paths GDScript can intercept.
Crashes are persisted to disk because the process is dying and can't ship live. The next game start drains the queue and flushes everything as crash_reported events. The server splits those into a separate session_crashes table, so your event stream stays clean.
Each crash report carries:
NullReferenceException or Cannot read properties of undefined).Find these under Settings > Events on each game. The SDK reads them on startup via the existing event-config fetch, so changes take effect on the next session start.
Crashes enabled: master switch. Default on. Turn it off if you already run a dedicated crash reporter and don't want duplicate captures.Recent events count: how many events from the in-memory ring buffer to ship alongside the stack. Default 5, range 0–50. Higher means more context for the AI, but more disk on crash.Sample rate: fraction of crashes the SDK actually persists. Default 1.0 (everything). Drop this for very-high-crash games so the dashboard stays useful./games/<slug>/crashes shows the grouped-by-signature list with a build filter. Each row links into a detail page keyed on the stack signature, so sharing the link survives recurrences. The detail page shows the latest occurrence, the full stack, and an Acknowledge button that dismisses every crash matching that signature for the game (rollup counts stay accurate; the suppression is presentational).
The game-tabs bar shows a red badge on Crashes with the unacknowledged occurrence count (capped at 99+). Sessions that ended in a crash get a small linked badge next to the status badge. Click through to the signature.
Browser games shipped through a bundler (Webpack, Rollup, Vite, Turbopack) produce minified stacks that are unreadable on their own. Upload the .map files alongside each build, and the server resolves every new crash automatically. Re-symbolication is fire-and-forget after ingest; the dashboard renders the resolved stack with a Show raw toggle.
for f in dist/**/*.map; do
curl -X POST https://playloop.gg/api/v1/games/<slug>/symbols \
-H "Authorization: Bearer $PLAYLOOP_MANAGEMENT_KEY" \
-F "buildVersion=$BUILD_VERSION" \
-F "platform=typescript-browser" \
-F "files=@$f"
doneRe-uploads of the same bytes are no-ops (uniqueness key on gameId + buildVersion + platform + fileHash), so the loop is safe to bake into every CI run. Manage your uploads under Settings > Symbols on the game. Copy the curl snippet straight from there.
Before: at u (bundle.abc.js:1:12345)
After: at handleClick (components/my-component.tsx:42:7)
Groups that resolved to the same source line collapse automatically. A single bug across two build hashes shows as one signature, not two.
Heavy minification on server code is rare; Node 24 has --enable-source-maps on by default, so most Node stacks are already readable. Upload maps with platform=typescript-node if you need them.
IL2CPP retail builds strip method names and line numbers. Symbol files live on your machine alongside the build output; uploading 500 MB of symbol blobs per platform per build isn't practical. Instead, the Unity SDK ships an Editor window that resolves locally.
The flow:
EditorPrefs, shared with the Event Config window).ndk-stack -sym /path/to/symbols for Android, atos -o build.dSYM/...for iOS). The raw stack is piped to the tool's stdin; its stdout becomes the resolved stack.Type ndk-stack -sym /Users/me/builds/0.4.9/symbols once. The next Android crash opens the dialog pre-filled. Type, run, done.
Paste a Slack or Discord incoming-webhook URL on Settings > Crash notifications and the next crash with a never-seen-before signature pings that channel with the error message, build, platform, and a deep link to the crash detail page.
Deduped by stack signature over 24 hours, so a crash storm against the same bug pings once per day. A different bug in the same window gets its own ping.
In Slack: workspace settings > Apps > Incoming Webhooks > Add to Slack > pick a channel > copy the Webhook URL. Paste into the Slack field on the settings page. Hit Send test to confirm the wiring.
In Discord: channel settings > Integrations > Webhooks > New Webhook > pick the target channel > Copy Webhook URL. Paste into the Discord field. Hit Send test.
A header with the game name and the error. The build version and platform are listed underneath, and a button opens the crash detail page (where the dev can Acknowledge the signature to suppress further notifications).
The webhook URL is encrypted at rest (AES-256-GCM). The hostname is restricted to slack.com and discord.comdomains so a typo can't turn the field into an arbitrary outbound POST target. Private and loopback addresses are blocked at send time as defense-in-depth.
| SDK | Capture | Resolves automatically? |
|---|---|---|
| Python | sys.excepthook + threading.excepthook | Yes. Python tracebacks are already symbolic. |
| TypeScript | Browser: window.onerror + unhandledrejection. Node: uncaughtException. | Yes when you've uploaded source maps. Minified stacks show a banner pointing at Settings > Symbols otherwise. |
| Unity | Application.logMessageReceived filtered to LogType.Exception. | Editor, Mono, dev builds: yes (file + line already present). IL2CPP retail: use the Editor window above. |
| Unreal | FCoreDelegates::OnHandleSystemError for OS-level crashes, plus explicit Client->ReportCrash(message, stackTrace) for caught gameplay exceptions. Both gated by the bEnableCrashHandler config flag. | Yes when symbols are present. Managed Blueprint errors and PIE-only soft asserts don't trip the OS trap; report those with ReportCrash. |
| Godot | Explicit Playloop.report_crash(stack, message) from your error handlers (GDScript doesn't expose a global trap). | Yes. GDScript stacks always include file + line. |
Crashes from a process that's already in the wrong state (deep native code corruption, OS-level forced kills, OOM) can't be captured by any user-space handler. Plan for most uncaught exceptions, not every termination.
These docs are evolving. Playloop is in active development ahead of launch, so APIs and details may change as we polish.