Quick answer: Capture crashes from Unreal shipping builds with the callstack and device context, and keep your symbols so the stripped shipping callstack can be symbolicated into readable function names. The shipping configuration removes the debugging tools you rely on, so external capture and symbol management are essential.

There is a wide gap between how Unreal Engine behaves in the editor or a development build and how it behaves in the shipping configuration your players actually run. Shipping strips assertions, removes much of the logging, and optimizes aggressively, which means crashes that never appear during development can hit players, and when they do, the callstack is a wall of stripped, optimized frames. Setting up real crash reporting for shipping builds, with proper symbol management, is what makes those player crashes diagnosable.

Shipping is a different game

Unreal build configurations are not just performance tiers, they change behavior. Development builds keep assertions, verbose logging, and editor-adjacent safety. Shipping strips most of that for performance and size, so code paths that an assertion would have caught in development can fail silently or crash in shipping, and the verbose logs you would normally read are simply not there.

This is why testing only in the editor or in development builds is insufficient. The configuration your players run is the one that matters, and it is the one most likely to surface optimizer-related bugs, stripped-assertion crashes, and platform-specific failures. You have to capture crashes from the actual shipping build to see what players see.

Capture the callstack and crash context

When a shipping build crashes, you want the callstack, the crashing thread, the engine and game version, and the device context. Unreal generates crash data through its own crash handling, and the key is getting that data off the player machine and into a place where you can analyze it, rather than leaving it as a local report the player will never send you.

Beyond the callstack, capture the surrounding context: the current level or map, the platform and hardware, available memory, and any custom state your game tracks. A callstack tells you where the crash happened in code, but the level and game state tell you what the player was doing, and together they are far more actionable than either alone.

Manage your symbols

The single most important practice for Unreal shipping crash reporting is symbol management. Shipping callstacks are stripped and optimized, so a raw crash report shows addresses or mangled frames, not readable function names. To turn that back into something you can read, you need the debug symbols, PDB files on Windows, dSYM on Apple platforms, generated for that exact build.

Store the symbols for every shipping build you release, keyed to the build version, and never overwrite them. When a crash comes in from version 1.3.2, you symbolicate it against the 1.3.2 symbols to recover the real function names and line numbers. Lose the symbols and your shipping crash reports become nearly worthless, so treat symbol archival as part of your release process, not an afterthought.

Capture device context for clustering

Unreal games ship to a wide range of hardware, and crashes often cluster by platform, GPU, or driver. Capture the platform, CPU, GPU and driver version, and memory with every crash so you can group failures and spot when a crash is specific to one configuration rather than universal.

This clustering is what turns a pile of individual crashes into a prioritized list. A crash concentrated on one GPU driver is a different problem from one that hits every platform equally, and you can only tell the difference if the device context rides along with each report. Without it, you are guessing at scope and cause.

Setting it up with Bugnet

Bugnet captures crashes from your Unreal shipping builds and attaches the callstack and device context, uploading reports so they reach your dashboard instead of dying on the player machine. You associate each report with its build version, which is the key to symbolication and to tracking crashes across releases.

Add custom fields for the level, game mode, and any state your game tracks, and group identical crashes into occurrence counts to see scale and prioritize. With shipping crashes flowing into one place, the gap between how your game behaves in the editor and how it behaves in players hands finally becomes visible and fixable.

Build symbolication into your release process

The discipline that makes all of this work is tying symbols to versions automatically as part of your build pipeline. Every time you produce a shipping build, archive its symbols against the version string that the build reports in its crashes. If those two things always match, symbolication is reliable, and if they ever drift, your reports become unreadable for that release.

Make this a checklist item in your release process so it never gets skipped under deadline pressure. A shipping crash reporting setup is only as good as your worst-archived build, because that is the version where a critical crash will inevitably appear, and you will be very glad you kept the symbols when it does.

Shipping strips your tools. Symbols are how you get them back when it counts.