Project Setup
Let’s create a complete plugin from scratch that displays a simple window and registers a command.
Create a New Project
Create a new .NET class library: dotnet new classlib -n MyFirstPlugin -f net8.0
cd MyFirstPlugin
Or use Visual Studio:
File → New → Project
Select “Class Library (.NET Core)”
Choose .NET 8.0
Name it “MyFirstPlugin”
Configure the Project File
Edit MyFirstPlugin.csproj to include Dalamud dependencies: < Project Sdk = "Microsoft.NET.Sdk" >
< PropertyGroup >
< TargetFramework > net8.0-windows </ TargetFramework >
< LangVersion > latest </ LangVersion >
< AllowUnsafeBlocks > true </ AllowUnsafeBlocks >
< Platforms > x64 </ Platforms >
< Nullable > enable </ Nullable >
< ProduceReferenceAssembly > false </ ProduceReferenceAssembly >
< AppendTargetFrameworkToOutputPath > false </ AppendTargetFrameworkToOutputPath >
< OutputPath > bin\$(Configuration)\ </ OutputPath >
</ PropertyGroup >
< ItemGroup >
< PackageReference Include = "DalamudPackager" Version = "2.1.13" />
</ ItemGroup >
< PropertyGroup >
< DalamudLibPath > $(appdata)\XIVLauncher\addon\Hooks\dev\ </ DalamudLibPath >
</ PropertyGroup >
< ItemGroup >
< Reference Include = "Dalamud" >
< HintPath > $(DalamudLibPath)Dalamud.dll </ HintPath >
< Private > false </ Private >
</ Reference >
< Reference Include = "ImGui.NET" >
< HintPath > $(DalamudLibPath)ImGui.NET.dll </ HintPath >
< Private > false </ Private >
</ Reference >
< Reference Include = "Lumina" >
< HintPath > $(DalamudLibPath)Lumina.dll </ HintPath >
< Private > false </ Private >
</ Reference >
< Reference Include = "Lumina.Excel" >
< HintPath > $(DalamudLibPath)Lumina.Excel.dll </ HintPath >
< Private > false </ Private >
</ Reference >
</ ItemGroup >
</ Project >
Set <Private>false</Private> for Dalamud references. This prevents bundling Dalamud DLLs with your plugin.
Create the Plugin Manifest
Create MyFirstPlugin.json in your project root: {
"Author" : "Your Name" ,
"Name" : "My First Plugin" ,
"Punchline" : "A simple example plugin" ,
"Description" : "This plugin demonstrates basic Dalamud functionality." ,
"InternalName" : "MyFirstPlugin" ,
"RepoUrl" : "https://github.com/yourusername/MyFirstPlugin" ,
"Tags" : [ "example" , "ui" ]
}
The DalamudPackager will automatically generate the full manifest during build.
Implementing the Plugin
Main Plugin Class
Create Plugin.cs with the core plugin implementation:
using Dalamud . Game . Command ;
using Dalamud . IoC ;
using Dalamud . Plugin ;
using Dalamud . Plugin . Services ;
namespace MyFirstPlugin ;
public sealed class Plugin : IDalamudPlugin
{
private const string CommandName = "/pmyfirst" ;
private readonly IDalamudPluginInterface pluginInterface ;
private readonly ICommandManager commandManager ;
private readonly IChatGui chatGui ;
private PluginUI ui ;
public Plugin (
IDalamudPluginInterface pluginInterface ,
ICommandManager commandManager ,
IChatGui chatGui )
{
this . pluginInterface = pluginInterface ;
this . commandManager = commandManager ;
this . chatGui = chatGui ;
// Create UI
this . ui = new PluginUI ( this );
this . pluginInterface . UiBuilder . Draw += this . ui . Draw ;
this . pluginInterface . UiBuilder . OpenConfigUi += () => this . ui . IsVisible = true ;
// Register command
this . commandManager . AddHandler ( CommandName , new CommandInfo ( OnCommand )
{
HelpMessage = "Opens the My First Plugin window" ,
ShowInHelp = true
});
// Send welcome message
this . chatGui . Print ( "My First Plugin loaded! Use /pmyfirst to open." );
}
private void OnCommand ( string command , string args )
{
this . ui . IsVisible = ! this . ui . IsVisible ;
}
public void Dispose ()
{
// Unregister command
this . commandManager . RemoveHandler ( CommandName );
// Clean up UI
this . pluginInterface . UiBuilder . Draw -= this . ui . Draw ;
this . ui . Dispose ();
}
}
UI Implementation
Create PluginUI.cs to handle the interface:
using System ;
using System . Numerics ;
using ImGuiNET ;
namespace MyFirstPlugin ;
public class PluginUI : IDisposable
{
private readonly Plugin plugin ;
public bool IsVisible { get ; set ; }
private int clickCount = 0 ;
public PluginUI ( Plugin plugin )
{
this . plugin = plugin ;
}
public void Draw ()
{
if ( ! IsVisible )
return ;
DrawMainWindow ();
}
private void DrawMainWindow ()
{
ImGui . SetNextWindowSize ( new Vector2 ( 400 , 300 ), ImGuiCond . FirstUseEver );
ImGui . SetNextWindowSizeConstraints ( new Vector2 ( 300 , 200 ), new Vector2 ( float . MaxValue , float . MaxValue ));
if ( ImGui . Begin ( "My First Plugin" , ref IsVisible , ImGuiWindowFlags . NoScrollbar | ImGuiWindowFlags . NoScrollWithMouse ))
{
ImGui . Text ( "Welcome to Dalamud plugin development!" );
ImGui . Spacing ();
ImGui . TextColored ( new Vector4 ( 0.5f , 1.0f , 0.5f , 1.0f ), "This is a colored text" );
ImGui . Spacing ();
if ( ImGui . Button ( "Click me!" ))
{
clickCount ++ ;
}
ImGui . SameLine ();
ImGui . Text ( $"Clicked { clickCount } times" );
ImGui . Spacing ();
ImGui . Separator ();
ImGui . Spacing ();
if ( ImGui . CollapsingHeader ( "Information" ))
{
ImGui . BulletText ( "This plugin demonstrates basic functionality" );
ImGui . BulletText ( "You can draw UI using ImGui" );
ImGui . BulletText ( "Commands can be registered and handled" );
ImGui . BulletText ( "Services are injected via constructor" );
}
}
ImGui . End ();
}
public void Dispose ()
{
// Clean up any resources here
}
}
Understanding the Code
Dependency Injection
Dalamud uses constructor injection to provide services:
public Plugin (
IDalamudPluginInterface pluginInterface ,
ICommandManager commandManager ,
IChatGui chatGui )
You can request any Dalamud service as a constructor parameter. See the Services API reference for available services.
The Plugin Interface
IDalamudPluginInterface is your main gateway to Dalamud:
/plugin-dev/plugin-interface.mdx#L1-L100
- ** UiBuilder **: Register UI draw handlers
- ** Reason **: Why the plugin was loaded
- ** ConfigDirectory **: Where to save configuration files
- ** GetPluginConfig () **: Load saved configuration
- ** SavePluginConfig () **: Save configuration
See the Plugin Interface guide for full details.
Command Registration
this . commandManager . AddHandler ( CommandName , new CommandInfo ( OnCommand )
{
HelpMessage = "Opens the My First Plugin window" ,
ShowInHelp = true // Shows in /xlhelp
});
Always remove commands in Dispose():
this . commandManager . RemoveHandler ( CommandName );
UI Drawing
Subscribe to the Draw event:
this . pluginInterface . UiBuilder . Draw += this . ui . Draw ;
This gets called every frame. Keep your draw code efficient!
Always unsubscribe from events in Dispose() to prevent memory leaks.
Building and Testing
Build the Project
Or press Ctrl+Shift+B in Visual Studio.
Copy to Dev Plugins
The build output should be copied to: %AppData%\XIVLauncher\devPlugins\MyFirstPlugin\
You can automate this with a post-build event in your .csproj: < Target Name = "PostBuild" AfterTargets = "PostBuildEvent" >
< Exec Command = "xcopy /Y /I /E " $(TargetDir)* " " $(AppData)\XIVLauncher\devPlugins\$(ProjectName)\ " " />
</ Target >
Load in Game
Launch FFXIV with Dalamud
Type /xldev to open the developer menu
Go to the “Plugin Dev” tab
Click “Load Plugin” next to your plugin
Type /pmyfirst to open your window
Configuration
Add persistent configuration to your plugin:
using Dalamud . Configuration ;
namespace MyFirstPlugin ;
public class Configuration : IPluginConfiguration
{
public int Version { get ; set ; } = 1 ;
// Add your settings here
public bool SomeOption { get ; set ; } = true ;
public string SomeText { get ; set ; } = "Default value" ;
public void Save ( IDalamudPluginInterface pluginInterface )
{
pluginInterface . SavePluginConfig ( this );
}
}
Load configuration in your plugin:
private readonly Configuration config ;
public Plugin ( IDalamudPluginInterface pluginInterface , .. .)
{
this . config = pluginInterface . GetPluginConfig () as Configuration ?? new Configuration ();
}
Save when settings change:
this . config . SomeOption = newValue ;
this . config . Save ( this . pluginInterface );
Common Patterns
If you need to access your plugin instance from UI or other classes: public sealed class Plugin : IDalamudPlugin
{
public static Plugin Instance { get ; private set ; } = null ! ;
public Plugin (...)
{
Instance = this ;
// ...
}
public void Dispose ()
{
Instance = null ! ;
// ...
}
}
Create a central service container: public class Services
{
public IDalamudPluginInterface PluginInterface { get ; init ; } = null ! ;
public ICommandManager CommandManager { get ; init ; } = null ! ;
public IChatGui ChatGui { get ; init ; } = null ! ;
// Add more services as needed
public static void Initialize ( Plugin plugin , ...)
{
Instance = new Services
{
PluginInterface = plugin . PluginInterface ,
// ...
};
}
public static Services Instance { get ; private set ; } = null ! ;
}
Support multiple languages: var langCode = this . pluginInterface . UiLanguage ;
var text = langCode switch
{
"ja" => "こんにちは" ,
"de" => "Hallo" ,
"fr" => "Bonjour" ,
_ => "Hello"
};
// Listen for language changes
this . pluginInterface . LanguageChanged += OnLanguageChanged ;
Next Steps
Plugin Manifest Learn about all manifest fields and configuration options
Plugin Interface Explore all IDalamudPluginInterface methods and properties
UI & ImGui Create more complex user interfaces
Debugging Debug your plugin effectively