Skip to main content
Dalamud uses a sophisticated Inversion of Control (IoC) container to manage services and inject dependencies into plugins. This system ensures clean separation of concerns and makes testing easier.

Overview

Dependency Injection (DI) in Dalamud allows plugins to receive services through:

Constructor Injection

Services injected as constructor parameters

Property Injection

Services injected into properties marked with [PluginService]

Constructor Injection

The recommended way to receive services is through your plugin’s constructor:
using Dalamud.Plugin;
using Dalamud.Plugin.Services;

public class MyPlugin : IDalamudPlugin
{
    private readonly DalamudPluginInterface pluginInterface;
    private readonly ICommandManager commands;
    private readonly IChatGui chat;
    
    public MyPlugin(
        DalamudPluginInterface pluginInterface,
        ICommandManager commands,
        IChatGui chat)
    {
        this.pluginInterface = pluginInterface;
        this.commands = commands;
        this.chat = chat;
        
        // Services are now available for use
        commands.AddHandler("/mycommand", new CommandInfo(OnCommand)
        {
            HelpMessage = "My plugin command"
        });
    }
    
    public void Dispose()
    {
        commands.RemoveHandler("/mycommand");
    }
}
Constructor injection is type-safe and makes your dependencies explicit. The compiler will catch errors if you reference a service that doesn’t exist.

Property Injection

You can also use property injection with the [PluginService] attribute:
using Dalamud.IoC;
using Dalamud.Plugin;
using Dalamud.Plugin.Services;

public class MyPlugin : IDalamudPlugin
{
    [PluginService]
    public static IDataManager DataManager { get; set; } = null!;
    
    [PluginService]
    public static IChatGui Chat { get; set; } = null!;
    
    [PluginService]
    public static ICommandManager Commands { get; set; } = null!;
    
    public MyPlugin(DalamudPluginInterface pluginInterface)
    {
        // Properties are injected AFTER constructor runs
        // but BEFORE plugin becomes active
    }
    
    public void Dispose()
    {
        // Cleanup
    }
}
Property-injected services are set AFTER the constructor completes. Don’t try to use them in your constructor!

Static vs Instance Properties

Property injection works with both static and instance properties:
[PluginService]
public static IClientState ClientState { get; set; } = null!;
Static properties are convenient but can complicate testing. Consider using constructor injection for better testability.

The ServiceContainer

Dalamud’s IoC is powered by the ServiceContainer class:
// From Dalamud/IoC/Internal/ServiceContainer.cs:22
internal class ServiceContainer : IServiceType
{
    private readonly Dictionary<Type, ObjectInstance> instances = [];
    private readonly Dictionary<Type, Type> interfaceToTypeMap = [];
    
    // ...
}

How Services Are Resolved

When your plugin is constructed, the container resolves dependencies:
1

Constructor Discovery

The container finds an eligible constructor:
// From ServiceContainer.cs:217
private ConstructorInfo? FindApplicableCtor(Type type, object[] scopedObjects)
{
    var ctors = type.GetConstructors(ctorFlags);
    foreach (var ctor in ctors)
    {
        if (this.ValidateCtor(ctor, allValidServiceTypes))
            return ctor;
    }
    return null;
}
2

Parameter Resolution

Each constructor parameter is resolved:
// From ServiceContainer.cs:108
var resolvedParams =
    await Task.WhenAll(
        ctor.GetParameters()
            .Select(p => p.ParameterType)
            .Select(type => this.GetService(type, scopeImpl, scopedObjects)));
3

Instance Creation

The plugin is instantiated with resolved parameters:
// From ServiceContainer.cs:112
var instance = RuntimeHelpers.GetUninitializedObject(objectType);
4

Property Injection

Properties marked with [PluginService] are injected:
// From ServiceContainer.cs:115
await this.InjectProperties(instance, scopedObjects, scope);
5

Constructor Invocation

Finally, the constructor runs:
// From ServiceContainer.cs:121
await Task.Factory.StartNew(
    () => ctor.Invoke(instance, resolvedParams),
    CancellationToken.None,
    TaskCreationOptions.LongRunning,
    TaskScheduler.Default).ConfigureAwait(false);

Service Scoping

Dalamud supports both singleton and scoped services:

Singleton Services

Most Dalamud services are singletons shared across all plugins:
// These are singleton services
public MyPlugin(IClientState clientState, IDataManager dataManager)
{
    // Same instance shared by all plugins
}

Scoped Services

Some services are scoped to individual plugins:
// From ServiceContainer.cs:184
if (serviceType.GetCustomAttribute<ServiceManager.ScopedServiceAttribute>() != null)
{
    if (scope == null)
    {
        throw new InvalidOperationException(
            $"Failed to create {serviceType.FullName ?? serviceType.Name}, is scoped but no scope provided");
    }
    
    return await scope.CreatePrivateScopedObject(serviceType, scopedObjects);
}
Plugin-specific services like DalamudPluginInterface are scoped to each plugin instance.

Interface Resolution

Services can be resolved by interface or concrete type:
// Both of these work:
public MyPlugin(IClientState clientState) { }  // Interface
public MyPlugin(ClientState clientState) { }   // Concrete type (not recommended)
The container maintains a mapping:
// From ServiceContainer.cs:68
public void RegisterInterfaces(Type type)
{
    var resolveViaTypes = type
                          .GetCustomAttributes()
                          .OfType<ResolveViaAttribute>()
                          .Select(x => x.GetType().GetGenericArguments().First());
    foreach (var resolvableType in resolveViaTypes)
    {
        this.interfaceToTypeMap[resolvableType] = type;
    }
}
Always use interfaces (like IClientState) rather than concrete types. This future-proofs your code against internal Dalamud changes.

Available Services

Plugins can inject any service marked with [PluginInterface]:
// From Dalamud/IoC/PluginInterfaceAttribute.cs:7
[AttributeUsage(AttributeTargets.Class)]
public class PluginInterfaceAttribute : Attribute
{
}
Commonly used services include:
Core plugin interface providing access to UI builder, configuration, and plugin metadata:
public MyPlugin(DalamudPluginInterface pluginInterface)
{
    pluginInterface.UiBuilder.Draw += DrawUI;
    var configDir = pluginInterface.ConfigDirectory;
}
Access player and character state:
public MyPlugin(IClientState clientState)
{
    var player = clientState.LocalPlayer;
    var territory = clientState.TerritoryType;
    clientState.TerritoryChanged += OnTerritoryChanged;
}
Register chat commands:
public MyPlugin(ICommandManager commands)
{
    commands.AddHandler("/mycommand", new CommandInfo(OnCommand)
    {
        HelpMessage = "My command"
    });
}
Print messages to chat:
public MyPlugin(IChatGui chat)
{
    chat.Print("Hello from my plugin!");
}
Access game data:
public MyPlugin(IDataManager dataManager)
{
    var item = dataManager.GetExcelSheet<Item>()?.GetRow(1);
}
Subscribe to game update ticks:
public MyPlugin(IFramework framework)
{
    framework.Update += OnFrameworkUpdate;
}
For a complete list of services, see the API Reference.

Service Attributes

Dalamud services are marked with attributes indicating their lifetime:
// From ServiceManager.cs:650
[AttributeUsage(AttributeTargets.Class)]
public class ProvidedServiceAttribute : ServiceAttribute { }

[AttributeUsage(AttributeTargets.Class)]
public class EarlyLoadedServiceAttribute : ServiceAttribute { }

[AttributeUsage(AttributeTargets.Class)]
public class BlockingEarlyLoadedServiceAttribute : EarlyLoadedServiceAttribute { }

[AttributeUsage(AttributeTargets.Class)]
public class ScopedServiceAttribute : ServiceAttribute { }
Manually provided by Dalamud during startup:
[ServiceManager.ProvidedService]
internal class ServiceContainer : IServiceType

Dependency Validation

The container validates dependencies at startup:
// From ServiceContainer.cs:242
private bool ValidateCtor(ConstructorInfo ctor, Type[] validTypes)
{
    bool IsTypeValid(Type type)
    {
        var contains = validTypes.Any(x => x.IsAssignableTo(type));
        return contains || type.GetCustomAttribute<ServiceManager.ScopedServiceAttribute>() != null;
    }
    
    var parameters = ctor.GetParameters();
    foreach (var parameter in parameters)
    {
        if (!IsTypeValid(parameter.ParameterType))
        {
            Log.Error("Ctor from {DeclaringType}: Failed to validate {TypeName}",
                      ctor.DeclaringType?.FullName, parameter.ParameterType.FullName!);
            return false;
        }
    }
    
    return true;
}
If your plugin requests a service that doesn’t exist, it will fail to load with a dependency resolution error.

Testing with DI

Constructor injection makes testing easier:
// In your tests, you can provide mock services
var mockCommands = new Mock<ICommandManager>();
var mockChat = new Mock<IChatGui>();

var plugin = new MyPlugin(
    mockPluginInterface.Object,
    mockCommands.Object,
    mockChat.Object
);

// Verify behavior
mockCommands.Verify(x => x.AddHandler("/mycommand", It.IsAny<CommandInfo>()), Times.Once);

Best Practices

Constructor injection is more explicit and testable:
// Good
public MyPlugin(IClientState clientState, IChatGui chat)
{
    this.clientState = clientState;
    this.chat = chat;
}

// Less ideal
[PluginService] public static IClientState ClientState { get; set; } = null!;
[PluginService] public static IChatGui Chat { get; set; } = null!;
Always request services by their interface, not concrete type:
// Good
public MyPlugin(IClientState clientState)

// Bad
public MyPlugin(ClientState clientState)
Store injected services as private readonly fields:
private readonly IClientState clientState;
private readonly IChatGui chat;

public MyPlugin(IClientState clientState, IChatGui chat)
{
    this.clientState = clientState;
    this.chat = chat;
}
Only inject services you actually use:
// Bad - injecting services "just in case"
public MyPlugin(
    IClientState clientState,
    IDataManager dataManager,
    IGameGui gameGui,
    ITextureProvider textures,
    ITargetManager targets,
    // ... 10 more services you don't use
)

Next Steps

Service Locator

Learn about the Service<T> pattern for accessing services

Plugin Lifecycle

Understand when services become available

Architecture

See how services fit into Dalamud’s architecture

API Reference

Browse all injectable services

Build docs developers (and LLMs) love