Skip to main content
Plugins in Dalamud follow a well-defined lifecycle from discovery to unload. Understanding this lifecycle is crucial for building robust plugins that integrate cleanly with the framework.

Lifecycle Overview

Plugin Discovery

Dalamud discovers plugins by scanning the plugin directory for valid plugin manifests.

Plugin Manifest

Every plugin must have a manifest file (.json) describing its metadata:
{
  "Author": "YourName",
  "Name": "My Plugin",
  "InternalName": "MyPlugin",
  "AssemblyVersion": "1.0.0.0",
  "Description": "Does something cool",
  "ApplicableVersion": "any",
  "DalamudApiLevel": 9,
  "LoadRequiredState": 0,
  "LoadSync": false,
  "LoadPriority": 0
}
The DalamudApiLevel must match the current Dalamud major version. For Dalamud 9.x, this value must be 9.

Load States

Plugins can specify when they should be loaded using LoadRequiredState:
Default behavior. Plugin loads when drawing facilities are available.
{
  "LoadRequiredState": 0
}
Use this for plugins that need ImGui or game UI access immediately.

Synchronous vs Asynchronous Loading

The LoadSync property controls whether your plugin blocks the boot process:
{
  "LoadSync": true
}
Synchronous plugins are loaded sequentially and can delay game startup. Only use LoadSync: true if your plugin must be ready before others load.

Loading Process

When a plugin is loaded, Dalamud follows these steps:
1

Assembly Loading

Dalamud creates an isolated AssemblyLoadContext for the plugin:
// From Dalamud/Plugin/Internal/Loader/PluginLoader.cs:34
this.context = (ManagedLoadContext)this.contextBuilder.Build();
This ensures plugins don’t conflict with each other or with Dalamud’s dependencies.
2

Type Discovery

Dalamud scans the plugin assembly for types implementing IDalamudPlugin:
public interface IDalamudPlugin : IDisposable
{
    // Your plugin class implements this
}
3

Dependency Injection

The IoC container resolves all constructor and property dependencies:
// Example plugin constructor
public MyPlugin(DalamudPluginInterface pluginInterface, CommandManager commands)
{
    // Dependencies automatically injected
}
See Dependency Injection for details.
4

Constructor Execution

Your plugin’s constructor runs in a long-running task to avoid thread pool exhaustion:
// From Dalamud/IoC/Internal/ServiceContainer.cs:121
await Task.Factory.StartNew(
    () => ctor.Invoke(instance, resolvedParams),
    CancellationToken.None,
    TaskCreationOptions.LongRunning,
    TaskScheduler.Default).ConfigureAwait(false);
Plugin constructors can block but should complete quickly. Avoid long-running operations in your constructor.
5

Property Injection

After construction, services marked with [PluginService] are injected:
public class MyPlugin : IDalamudPlugin
{
    [PluginService] 
    public static IDataManager DataManager { get; set; } = null!;
}
6

Running State

The plugin is now fully loaded and operational. Dalamud tracks its state as PluginState.Loaded.

Plugin Startup Tracking

For synchronous plugins (LoadSync: true), Dalamud tracks startup progress:
// From Dalamud/Plugin/Internal/PluginManager.cs:627
this.StartupLoadTracking = new();
foreach (var pluginDef in pluginDefs.Where(x => x.Manifest.LoadSync))
{
    this.StartupLoadTracking.Add(pluginDef.Manifest!.InternalName, pluginDef.Manifest.Name);
}
This allows users to see which plugins are causing startup delays.

Running Phase

Once loaded, your plugin interacts with the game through Dalamud’s services:

Event Handling

public MyPlugin(DalamudPluginInterface pluginInterface, IFramework framework)
{
    framework.Update += OnFrameworkUpdate;
}

private void OnFrameworkUpdate(IFramework framework)
{
    // Called every game tick (~60 FPS)
}

UI Drawing

public MyPlugin(DalamudPluginInterface pluginInterface)
{
    pluginInterface.UiBuilder.Draw += DrawUI;
}

private void DrawUI()
{
    ImGui.Begin("My Window");
    ImGui.Text("Hello, world!");
    ImGui.End();
}

Command Registration

public MyPlugin(ICommandManager commands)
{
    commands.AddHandler("/mycommand", new CommandInfo(OnCommand)
    {
        HelpMessage = "Does something cool"
    });
}

private void OnCommand(string command, string args)
{
    // Handle command
}

Unloading Process

Plugins unload when the user disables them or when Dalamud shuts down:
1

Unload Request

The Plugin Manager initiates unload, changing state to PluginState.Unloading.
2

Dispose Called

Your plugin’s Dispose() method is invoked:
public void Dispose()
{
    // Clean up all resources
    framework.Update -= OnFrameworkUpdate;
    pluginInterface.UiBuilder.Draw -= DrawUI;
    commands.RemoveHandler("/mycommand");
}
Always unregister ALL event handlers and commands in Dispose! Failure to do so can cause crashes.
3

Grace Period

Dalamud waits for a grace period before unloading the assembly:
// From Dalamud/Plugin/Internal/PluginManager.cs:51
public const int PluginWaitBeforeFreeDefault = 1000; // 1 second
This ensures all pending operations complete.
4

Assembly Unload

The plugin’s AssemblyLoadContext is unloaded:
// From Dalamud/Plugin/Internal/Loader/PluginLoader.cs:116
if (this.context.IsCollectible)
    this.context.Unload();
5

Garbage Collection

The runtime collects the unloaded assembly:
GC.Collect();
GC.WaitForPendingFinalizers();

Error Handling

Dalamud handles plugin errors gracefully to prevent crashes:

Load Failures

If your plugin throws an exception during load:
// From Dalamud/EntryPoint.cs:298
var pm = Service<PluginManager>.GetNullable();
var plugin = pm?.FindCallingPlugin(new StackTrace(ex));
if (plugin != null)
{
    pluginInfo = $"Plugin that caused this:\n{plugin.Name}\n\nClick \"Yes\" and remove it.\n\n";
}
Users are notified and can remove the problematic plugin.

Runtime Exceptions

Exceptions in event handlers are caught and logged:
try
{
    // Your event handler
}
catch (Exception ex)
{
    Log.Error(ex, "Exception in plugin event handler");
}
Uncaught exceptions in framework events won’t crash the game, but may leave your plugin in an undefined state.

Plugin States

Plugins can be in one of several states:
StateDescription
UnloadedPlugin is installed but not loaded
LoadingPlugin is currently loading
LoadedPlugin is running normally
LoadErrorPlugin failed to load
UnloadingPlugin is being unloaded
DependencyResolutionFailedPlugin dependencies couldn’t be resolved

Best Practices

  • Keep constructors fast and simple
  • Only store references to injected services
  • Don’t start background work in the constructor
  • Don’t access game state that might not be ready
// Good
public MyPlugin(IFramework framework)
{
    this.framework = framework;
    this.framework.Update += OnUpdate;
}

// Bad
public MyPlugin(IFramework framework)
{
    Thread.Sleep(5000); // Never do this!
    var player = GetLocalPlayer(); // Might not be ready yet!
}
  • Always implement Dispose() properly
  • Unregister ALL event handlers
  • Remove ALL commands
  • Dispose all IDisposable resources
  • Set large data structures to null
public void Dispose()
{
    framework.Update -= OnUpdate;
    pluginInterface.UiBuilder.Draw -= DrawUI;
    commands.RemoveHandler("/mycommand");
    
    myLargeDataStructure = null;
    myDisposableResource?.Dispose();
}
  • Most Dalamud services are not thread-safe
  • Use Framework.RunOnFrameworkThread() for game state access
  • Be careful with background tasks
Task.Run(async () =>
{
    var data = await FetchDataFromApi();
    
    // Switch back to framework thread for UI updates
    await framework.RunOnFrameworkThread(() =>
    {
        UpdateUIWithData(data);
    });
});
  • Framework.Update runs every frame (~60 FPS)
  • Keep event handlers fast (<16ms)
  • Cache expensive computations
  • Use throttling for expensive operations
private DateTime lastCheck = DateTime.MinValue;

private void OnUpdate(IFramework framework)
{
    // Only check every second instead of 60 times per second
    if (DateTime.Now - lastCheck < TimeSpan.FromSeconds(1))
        return;
        
    lastCheck = DateTime.Now;
    DoExpensiveCheck();
}

Next Steps

Dependency Injection

Learn how to inject services into your plugin

Service Locator

Access services using the Service<T> pattern

Architecture

Understand Dalamud’s overall architecture

Plugin Template

Start building your first plugin

Build docs developers (and LLMs) love