Jump to content

Mod Loader/Plugin lifecycle

From Threadbox

Use a ModContext to own your plugin's registrations, wait for content before looking up items, and check optional support before using it. The examples below target Mod Loader 0.7.8.

New to C# plugins? Start with Write a C# mod to set up the project and game references.

Register your plugin

Add a dependency on Mod Loader, then register once in Awake. Keep the returned context until the plugin unloads.

using BepInEx;
using DressmakerMods;

[BepInPlugin("example.content-notes", "Content Notes", "1.0.0")]
[BepInDependency("yuu.dressmaker.mods", "0.7.8")]
public sealed class ContentNotes : BaseUnityPlugin
{
    private ModContext mod;

    private void Awake()
    {
        if (!ModLoaderApi.IsReady)
        {
            Logger.LogWarning("Content Notes could not start. Check the loader's compatibility report.");
            return;
        }

        mod = ModLoaderApi.Register(this);
        ModLifecycleApi.WhenContentReady(mod, OnContentReady);
    }

    private void OnContentReady()
    {
        Logger.LogInfo("Content registration finished.");
    }

    private void OnDestroy()
    {
        mod?.Dispose();
    }
}

Call the owned APIs and dispose their registrations on Unity's main thread. A context can also track an IDisposable from another API with mod.Track(registration). Disposing the context releases tracked resources in reverse order and continues if an individual cleanup fails. Dispose the context after any fabric-render callback has returned.

Choose the right readiness check

Check What it tells you
ModLoaderApi.IsReady Initial loader startup finished. Optional features may have been skipped. Check save loading separately before reading save data.
ModLifecycleApi.IsContentReady The startup content-registration stage finished. Some content may have been skipped; check item lookups before using their results.
ModLifecycleApi.WhenContentReady(mod, callback) Run after content registration, or immediately if that stage already finished. Deferred textures and models may still need to load.
ModLoaderApi.Supports(id, minimumVersion) The capability contract exists at the requested version and its required loader features are available.

Initialize anything the callback needs before calling WhenContentReady: it can run before the call returns. Check item lookups and Loading problems for skipped content.

React to saves and scene changes

Once you have a context, subscribe to the events your plugin needs. Check version 2 when using the priority overload:

if (ModLoaderApi.Supports("lifecycle-events", minimumVersion: 2))
{
    ModLifecycleApi.Subscribe(mod, ModLifecycleEvent.AfterSave,
        context => Logger.LogInfo("Saved slot " + context.SaveSlot),
        priority: 0);
}
Event Use it for
ContentReady Look up registered content. A late subscription runs immediately.
SaveLoading Clear state associated with the previous save. Wait for SaveLoaded before reading the incoming save.
SaveLoaded Read the loaded save and initialize your plugin's per-save state.
BeforeSave Prepare in-memory plugin data for the save attempt. The attempt can still fail or be cancelled.
AfterSave React after a confirmed game-save write. Check the log for separate plugin-data write errors.
SceneLoaded, ReturnedToTitle Refresh scene references or clear gameplay UI. A title scene can raise both events.
SaveDeleted Clear references to the deleted slot.

The callback receives Event, SaveSlot, and SceneName. Higher priorities run first. Equal priorities sort by plugin ID, then registration order. If a callback throws, that subscription is removed and the error is logged; other subscribers still run.

Skip an unavailable integration

Check support when enabling an integration, and again before operations that depend on it. If support is unavailable, disable the related control or overlay and keep your other features available.

if (ModLoaderApi.Supports("inventory-events"))
    ModInventoryApi.Refresh(ModInventoryKind.Fabrics);

Refresh returns false when no sidebar exists. Request refreshes outside inventory-refresh callbacks.

All capability checks return false before startup finishes. save-data also waits for content registration and is false when required save support is unavailable or protected read-only play is active. Before accessing mod.SaveData, also check that the game has loaded a save. Handle save-data errors too: a sidecar file may be damaged or use a newer format.

See capability IDs and plugin save-data and compatibility APIs. SafeLaunch skips optional loader features, so it is useful for testing whether your plugin handles missing support. Use Mod Manager to disable individual plugins separately.

Back to the Mod Loader guide