Quick answer: Make every report answer “was this modded?”: log the active mod list and load order at startup and whenever it changes, so it arrives with automatically captured errors, and attach it as a file to player reports. Catch exceptions thrown by mod code and log them with the mod's ID in the message, so they group per mod instead of mixing with yours. Then triage modded and vanilla reports differently.

Mod support is one of the best things you can do for a game's lifespan, and one of the worst things you can do to your crash dashboard. A popular mod that breaks after your patch can produce more reports than every bug in your own code. Without knowing which reports are modded, you cannot tell whether your update is broken or a mod is.

1. Record the mod list with every crash

The single most useful fact in a modded crash report is which mods were loaded, in which order. Two ways to get it there:

Unity
BugnetSDK.Instance.BugReported += (bugId, reportNumber, title) => {
    var text = string.Join("\n", ModLoader.LoadedMods.Select(m => $"{m.Id} {m.Version}"));
    BugnetSDK.Instance.UploadAttachmentBytes(bugId, "mods.txt", Encoding.UTF8.GetBytes(text), "text/plain");
};
Godot 4
Bugnet.bug_reported_id.connect(func(bug_id, _number, _title):
    var text = "\n".join(ModLoader.loaded_mod_ids())
    Bugnet.upload_attachment_bytes(bug_id, "mods.txt", text.to_utf8_buffer()))

ModLoader stands for your own mod system. The callback also fires when a report stacks onto an existing bug, so skip the upload if you have already sent the same list this session.

2. Make mod errors say which mod they came from

An exception inside mod code looks like an exception inside yours. Wrap the points where you call into mods (load, update hooks, event handlers), catch what they throw, and log it with the mod's ID first:

try { mod.OnUpdate(); }
catch (Exception e) {
    Debug.LogError($"[Mod:{mod.Id}] {e.GetType().Name}: {e.Message}");
    mod.Disable();   // one broken mod should not take the game down
}

Automatically captured errors are titled from the message, so these arrive as “[Error] [Mod:better-ui] NullReferenceException” and stack onto one bug per mod and error. Your own bugs keep their own titles. If you would rather not file mod errors at all, log them as warnings: Bugnet's Unity SDK files exceptions, errors and asserts, not warnings.

3. Triage modded and vanilla reports differently

Design choices that make mod crashes manageable

For crashes caused by mod loading itself, see how to stop one mod crash bringing down the whole game. Create a free Bugnet project to capture crashes with your mod list attached.

Frequently asked questions

How do I know if a crash report came from a modded game?

Log the loaded mods and their order at startup and whenever they change, so the list arrives in the recent log lines attached to automatically captured errors, and attach it as a file to player reports.

How do I stop mod errors mixing with my own bugs?

Catch exceptions from mod code where you call into it, and log them with the mod's ID at the start of the message. Automatically captured errors are titled from the message, so they group per mod.

Can I stop mod errors from being filed as bug reports?

Yes. Log them as warnings instead of errors. Bugnet's Unity SDK files exceptions, errors and asserts automatically but not warnings.

What should I fix first in a moddable game?

Crashes that happen with no mods loaded, then crashes your own update caused across many mods, such as an API change.

Can I attach files to a crash report from my game?

Yes. Bugnet's SDKs can upload attachments to a report from the report callback, such as a mod list, a log file or a save file, as text, JSON, image or video files up to 10 MB.

The first question for every crash in a moddable game is whether it happens without mods.