Skip to main content
While Dependency Injection is the recommended way for plugins to access services, Dalamud internally uses the Service Locator pattern through the Service<T> class. Understanding this pattern is useful for advanced plugin development and understanding Dalamud’s internals.
The Service<T> class is internal to Dalamud and not accessible to plugins. This page is for educational purposes to help you understand how Dalamud works internally.

What is Service Locator?

The Service Locator pattern is an alternative to dependency injection where components request services from a central registry:
// Instead of injection:
public MyClass(IClientState clientState) { }

// Service locator:
public MyClass()
{
    var clientState = Service<IClientState>.Get();
}
Dalamud uses Service<T> internally but provides dependency injection to plugins for better testability and clarity.

The Service<T> Class

Dalamud’s service locator is implemented through a generic Service<T> class:
// Usage in Dalamud internals:
var clientState = Service<ClientState>.Get();
var framework = Service<Framework>.Get();
var pluginManager = Service<PluginManager>.Get();

Service Registration

Services are registered during Dalamud startup:
// From ServiceManager.cs:135
public static void InitializeProvidedServices(
    Dalamud dalamud,
    ReliableFileStorage fs,
    DalamudConfiguration configuration,
    TargetSigScanner scanner,
    Localization localization)
{
    void ProvideAllServices()
    {
        // ServiceContainer MUST be first
        ProvideService(new ServiceContainer());
        
        ProvideService(dalamud);
        ProvideService(fs);
        ProvideService(configuration);
        ProvideService(scanner);
        ProvideService(localization);
    }
    
    ProvideAllServices();
}

Service Resolution

Services can be retrieved synchronously or asynchronously:
Blocks until the service is ready:
var clientState = Service<ClientState>.Get();
This can deadlock if called during service initialization. Use GetAsync() instead.

Service Initialization Order

Dalamud initializes services in a specific order to handle dependencies:
1

Provided Services

Manually created services are registered first:
// From Dalamud.cs:72
ServiceManager.InitializeProvidedServices(
    this, fs, configuration, scanner, localization);
These include:
  • ServiceContainer
  • Dalamud itself
  • ReliableFileStorage
  • DalamudConfiguration
  • TargetSigScanner
  • Localization
2

Early-Loaded Services

Services marked with [EarlyLoadedService] are created asynchronously:
// From ServiceManager.cs:191
public static async Task InitializeEarlyLoadableServices()
{
    // Discovers and loads early services
}
3

Blocking Services

Services marked with [BlockingEarlyLoadedService] block game startup:
// From PluginManager.cs:45
[ServiceManager.BlockingEarlyLoadedService(
    "Accommodation of plugins that blocks the game startup.")]
internal class PluginManager : IInternalDisposableService
4

Dependency Resolution

Services with dependencies are initialized after their dependencies:
// From ServiceManager.cs:250
var typeAsServiceT = ServiceHelpers.GetAsService(serviceType);
dependencyServicesMap[serviceType] = ServiceHelpers.GetDependencies(typeAsServiceT, false)
                                                   .Select(x => typeof(Service<>).MakeGenericType(x))
                                                   .ToList();

Service Dependencies

Services can declare dependencies using the [ServiceDependency] attribute:
// From PluginManager.cs:67
[ServiceManager.ServiceDependency]
private readonly DalamudConfiguration configuration = Service<DalamudConfiguration>.Get();

[ServiceManager.ServiceDependency]
private readonly Dalamud dalamud = Service<Dalamud>.Get();

[ServiceManager.ServiceDependency]
private readonly ProfileManager profileManager = Service<ProfileManager>.Get();
This ensures dependencies are initialized before the service constructor runs.

Service Lifecycle

Construction

Services are constructed with special handling:
// From ServiceManager.cs:363
tasks.Add((Task)typeof(Service<>)
                .MakeGenericType(serviceType)
                .InvokeMember(
                    nameof(Service<IServiceType>.StartLoader),
                    BindingFlags.InvokeMethod | BindingFlags.Static | BindingFlags.NonPublic,
                    null,
                    null,
                    [startLoaderArgs]));

Unloading

Services are unloaded in reverse dependency order:
// From ServiceManager.cs:444
public static void UnloadAllServices()
{
    // Build dependency map
    var dependencyServicesMap = new Dictionary<Type, IReadOnlyCollection<Type>>();
    
    // Unload in reverse order
    unloadOrder.Reverse();
    foreach (var type in unloadOrder)
    {
        typeof(Service<>)
                .MakeGenericType(type)
                .InvokeMember("Unset", BindingFlags.InvokeMethod | BindingFlags.Static | BindingFlags.NonPublic,
                              null, null, null);
    }
}

Why Plugins Use DI Instead

Plugins don’t have access to Service<T> for several important reasons:
Service<T> is marked as internal, hiding implementation details:
// Plugins cannot access this
internal static class ServiceManager
This allows Dalamud to change its internal service implementation without breaking plugins.
Dependency injection makes plugin code more testable:
// Easy to test with mocks
public MyPlugin(IClientState clientState)
{
    this.clientState = clientState;
}

// Hard to test - requires service to be registered
public MyPlugin()
{
    this.clientState = Service<ClientState>.Get();
}
Constructor parameters make dependencies clear:
// Dependencies are obvious
public MyPlugin(IClientState clientState, IChatGui chat, ICommandManager commands)

// Dependencies are hidden in implementation
public MyPlugin()
{
    // What services does this use? Have to read the code!
}
DI container ensures services are available when plugins construct:
// Container guarantees clientState is ready
public MyPlugin(IClientState clientState)

// Might not be ready yet!
public MyPlugin()
{
    var clientState = Service<ClientState>.Get(); // Could block or fail
}

Internal Usage Examples

Here’s how Dalamud uses Service<T> internally:

Accessing Services

// From EntryPoint.cs:298
var pm = Service<PluginManager>.GetNullable();
var plugin = pm?.FindCallingPlugin(new StackTrace(ex));

Service Dependencies

// From PluginManager.cs:67
[ServiceManager.ServiceDependency]
private readonly DalamudConfiguration configuration = Service<DalamudConfiguration>.Get();

Async Service Access

// From PluginManager.cs:657
var framework = await Service<Framework>.GetAsync().ConfigureAwait(false);
await framework.RunOnTick(() => LoadPluginsSync(...));

Null-Safe Access

// From Dalamud.cs:178
var reportCrashesSetting = Service<DalamudConfiguration>.GetNullable()?.ReportShutdownCrashes ?? true;
var pmHasDevPlugins = Service<PluginManager>.GetNullable()?.InstalledPlugins.Any(x => x.IsDev) ?? false;

Service Registry

The ServiceContainer maintains the registry of all services:
// From ServiceContainer.cs:26
private readonly Dictionary<Type, ObjectInstance> instances = [];
private readonly Dictionary<Type, Type> interfaceToTypeMap = [];
Services can be looked up by concrete type or interface:
// From ServiceContainer.cs:195
private async Task<object?> GetSingletonService(Type serviceType, bool tryGetInterface = true)
{
    if (tryGetInterface && this.interfaceToTypeMap.TryGetValue(serviceType, out var implementingType))
        serviceType = implementingType;
    
    if (!this.instances.TryGetValue(serviceType, out var service))
        return null;
    
    var instance = await service.InstanceTask;
    return instance.Target;
}

Comparison: DI vs Service Locator

Advantages:
  • Explicit dependencies
  • Easy to test with mocks
  • Type-safe at compile time
  • Framework manages lifetime
Example:
public MyPlugin(IClientState clientState, IChatGui chat)
{
    // Dependencies injected automatically
}

Best Practices for Plugin Authors

Always use constructor or property injection in plugins:
// Good - explicit dependencies
public MyPlugin(IClientState clientState, IChatGui chat)
{
    this.clientState = clientState;
    this.chat = chat;
}
Service<T> is internal and not accessible to plugins:
// This won't compile - Service<T> is internal
var clientState = Service<ClientState>.Get();
Only inject services you actually use:
// Good - only what's needed
public MyPlugin(IClientState clientState)

// Bad - requesting everything
public MyPlugin(
    IClientState clientState,
    IDataManager dataManager,
    IGameGui gameGui,
    ITextureProvider textures,
    // ... 10 more you don't use
)

Understanding the Pattern

Knowing about the Service Locator pattern helps you understand:
  1. How Dalamud initializes - Services are registered and resolved through Service<T>
  2. Why DI is used for plugins - It’s a better pattern for plugin developers
  3. How services interact - Internal Dalamud code uses Service<T> to communicate
  4. The architecture - Dalamud uses a hybrid approach: Service Locator internally, DI for plugins
When reading Dalamud source code, you’ll see Service<T>.Get() frequently. This is the internal service locator pattern at work.

Next Steps

Dependency Injection

Learn how to use DI in your plugins

Architecture

Understand how services fit into the architecture

Plugin Lifecycle

See when services become available during plugin loading

API Reference

View the full list of services

Build docs developers (and LLMs) love