Write Your First Plugin
This tutorial builds a plugin that shows a notification and writes a line to its log each time a save finishes loading.
Requirements
To build:
- The .NET 10 SDK.
To run:
- Skyrim Special Edition, Anniversary Edition, or Skyrim VR.
- SKSE for the game's edition.
- Address Library for SKSE Plugins, or for Skyrim VR, VR Address Library for SKSEVR.
- The .NET 10 runtime (x64).
- SkyrimForge, installed in the game.
SkyrimForge is not yet published. Package.ps1 in the SkyrimForge repository builds both parts a plugin author needs:
artifacts\SkyrimForge-<version>.zip, which installs SkyrimForge: extract it into the game'sDatafolder, or install it with a mod manager.artifacts\packages\SkyrimForge.<version>.nupkg, the NuGet package a plugin references.
Create the Project
A plugin is a .NET 10 class library that references the SkyrimForge NuGet package. Create a folder named GreetingPlugin
containing GreetingPlugin.csproj:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="SkyrimForge" Version="0.1.0" />
</ItemGroup>
</Project>
NuGet finds the package through a package source. Add a nuget.config beside the project file that names
Package.ps1's output folder, artifacts\packages:
<configuration>
<packageSources>
<add key="SkyrimForge" value="C:\path\to\SkyrimForge\artifacts\packages" />
</packageSources>
</configuration>
Write the Plugin Class
A plugin class implements IPlugin. Its one public constructor declares the services it uses, and the framework supplies them when it constructs the plugin. Services lists every one. Start() runs once every plugin has been constructed.
This plugin uses three services:
- IGameEvents, to run code when a save finishes loading.
- IDebug, to show the notification.
- ILog, to write to the plugin's own log file.
Add GreetingPlugin.cs:
namespace SkyrimForge.ExamplePlugins;
/// <summary>Shows a notification and writes a line to its log each time a save finishes loading.</summary>
/// <remarks>The plugin the documentation's first tutorial builds.</remarks>
public sealed class GreetingPlugin(IGameEvents events, IDebug debug, ILog log) : IPlugin
{
private readonly IGameEvents events = events;
private readonly IDebug debug = debug;
private readonly ILog log = log;
/// <inheritdoc/>
public void Start()
{
events.OnSaveLoaded.Subscribe(_ =>
{
debug.Notification("Hello from GreetingPlugin.");
log.Write("A save finished loading.");
});
log.Write("GreetingPlugin started.");
}
}
That file is the example in the SkyrimForge repository. Change its namespace to GreetingPlugin, and add using SkyrimForge; at the top,
which the example omits because its namespace lies inside SkyrimForge.
Build and Install
dotnet build -c Release
Copy the contents of bin\Release\net10.0 into SKSE\Plugins\SkyrimForge\Plugins, under the game's Data folder or a mod folder.
SkyrimForge loads every plugin class in the DLLs there when the game starts.
Run It
Start the game through SKSE and load a save. The notification "Hello from GreetingPlugin." appears at the top left.
The plugin's log is SKSE\Plugins\SkyrimForge\Plugins\GreetingPlugin.GreetingPlugin.log, named for the full name of the plugin class.
Mod Organizer 2 writes it to its overwrite folder. The log contains "GreetingPlugin started." and, after each load, "A save finished loading."
A plugin that cannot be loaded is reported in a message box at the main menu, naming the plugin and the reason.
Next Steps
- Features Beyond the Bindings summarizes what the framework adds to the generated bindings.
- Services lists every service a plugin can declare.
- Debugging a Plugin attaches Visual Studio to the game.
- Papyrus Native Functions implements Papyrus script functions in C#.