Build a Valheim Mod: BepInEx, Harmony, and Jotunn
Valheim modding is more involved than games that mod through plain text or Lua: a Valheim mod is a compiled C# plugin (a .dll) loaded by BepInEx. The payoff is power - you can patch any part of the game and add real content. This guide covers the full path: the toolchain, a working plugin entry point, adding a custom item, patching game behaviour with Harmony, building, and running your mod on a dedicated server. It assumes you can write basic C# and have the .NET tooling installed.
The mental model: you do not edit the game's files. BepInEx loads your DLL alongside the game, Harmony rewrites game methods at runtime, and Jotunn gives you clean APIs to add items, recipes, and prefabs without fighting the engine. Use Jotunn for content and Harmony for behaviour.
What you'll build
- The stack - BepInEx, HarmonyX, Jotunn
- Set up the project - assemblies, publicizing, the mod stub
- Your first plugin - the entry-point class
- Add content with Jotunn - a custom item
- Change behaviour with Harmony - patching the game
- Build, install, test
- Distribute and run on a dedicated server
- Common pitfalls
1. The stack
| Layer | What it does |
|---|---|
| BepInEx | The plugin framework/loader. Your DLL goes in BepInEx/plugins and BepInEx loads it into the running game. Install the BepInExPack Valheim from Thunderstore - it is configured for Valheim. |
| HarmonyX | The runtime patching library (bundled with BepInEx). It lets you run your code before (prefix) or after (postfix) any existing game method without editing the original. |
| Jotunn (JVL) | The community modding library most content mods build on. It wraps the messy parts - registering items, recipes, prefabs, localization, config, and client/server sync - behind tidy managers. |
2. Set up the project
You need Visual Studio (with .NET desktop development) and the .NET Framework 4.6.2 targeting pack - Valheim's Unity runtime targets that framework. The fastest start is the official JotunnModStub template, which wires up the references and the post-build copy step for you.
- Install the BepInExPack Valheim from Thunderstore into your game folder.
- Start from the JotunnModStub project (it references BepInEx, HarmonyX, and Jotunn).
- Reference Valheim's own assemblies (chiefly
assembly_valheim.dll) fromValheim/Valheim_Data/Managed. - Publicize those assemblies. Game classes have many
privatemembers; a publicized copy exposes them so your code and Harmony patches can reach them. JotunnModStub runs the publicizer automatically as a prebuild step. - The build copies your compiled DLL into
<ValheimDir>\BepInEx\plugins.
3. Your first plugin (the entry point)
Every Valheim mod has one class that BepInEx instantiates. The attributes are what matter:
using BepInEx;
using HarmonyLib;
using Jotunn.Utils;
namespace MyFirstValheimMod
{
[BepInPlugin(PluginGUID, PluginName, PluginVersion)]
[BepInDependency(Jotunn.Main.ModGuid)]
[NetworkCompatibility(CompatibilityLevel.EveryoneMustHaveMod, VersionStrictness.Minor)]
internal class MyFirstValheimMod : BaseUnityPlugin
{
public const string PluginGUID = "com.yourname.myfirstvalheimmod";
public const string PluginName = "MyFirstValheimMod";
public const string PluginVersion = "0.1.0";
private readonly Harmony harmony = new Harmony(PluginGUID);
private void Awake()
{
Logger.LogInfo($"{PluginName} {PluginVersion} loaded");
harmony.PatchAll();
}
}
}
[BepInPlugin]declares your unique GUID, name, and version - use reverse-domain form for the GUID so it never collides.[BepInDependency(Jotunn.Main.ModGuid)]tells BepInEx to load Jotunn before your mod.[NetworkCompatibility(...)]is a Jotunn attribute that declares how your mod behaves across the network.EveryoneMustHaveModmeans server and all clients need it (correct for content mods); a purely client-side visual tweak would use a looser level. This is what stops "you have mods the server does not" kicks.harmony.PatchAll()inAwakeapplies every Harmony patch in your assembly (see section 5).
4. Add content with Jotunn
Jotunn's managers fire events when the game is ready for additions. To add items you wait for PrefabManager.OnVanillaPrefabsAvailable, then clone an existing item and register it. This adds an "EvilSword" cloned from the black-metal sword, craftable at the workbench:
using Jotunn.Configs;
using Jotunn.Entities;
using Jotunn.Managers;
// in Awake():
PrefabManager.OnVanillaPrefabsAvailable += AddClonedItems;
private void AddClonedItems()
{
ItemConfig evilSwordConfig = new ItemConfig();
evilSwordConfig.Name = "$item_evilsword";
evilSwordConfig.Description = "$item_evilsword_desc";
evilSwordConfig.CraftingStation = CraftingStations.Workbench;
evilSwordConfig.AddRequirement("Stone", 1, 1);
evilSwordConfig.AddRequirement("Wood", 1, 2);
CustomItem evilSword = new CustomItem("EvilSword", "SwordBlackmetal", evilSwordConfig);
ItemManager.Instance.AddItem(evilSword);
// run once, then unsubscribe
PrefabManager.OnVanillaPrefabsAvailable -= AddClonedItems;
}
Notes:
new CustomItem("EvilSword", "SwordBlackmetal", config)creates your prefab by cloning the vanillaSwordBlackmetal- inheriting its model, animations, and stats so you only override what you change.- The
$item_evilswordvalues are localization tokens, not literal text. Register the readable strings through Jotunn'sLocalizationManagerso your item shows a real name in-game. AddRequirement("Stone", 1, 1)sets the crafting cost (amount, and amount-per-upgrade). The item appears at the workbench automatically.
This is the pattern for most content - configure, clone or build, register through the relevant manager (ItemManager, PieceManager, PrefabManager). You rarely need Harmony just to add things.
5. Change behaviour with Harmony
When you want to alter what an existing game method does, you patch it. A Harmony patch is a static class targeting a method, with a Prefix (runs before) and/or Postfix (runs after). For example, to run code every time a player spawns:
using HarmonyLib;
[HarmonyPatch(typeof(Player), nameof(Player.OnSpawned))]
public static class Player_OnSpawned_Patch
{
[HarmonyPostfix]
private static void Postfix(Player __instance)
{
Jotunn.Logger.LogInfo($"{__instance.GetPlayerName()} spawned in");
}
}
__instance is Harmony's name for the object the method ran on. Your harmony.PatchAll() call in Awake discovers and applies this class automatically.
Verify every method signature against the real game. Class and method names like Player.OnSpawned come from Valheim's own code - confirm the exact names by decompiling assembly_valheim.dll (in Valheim_Data/Managed) with ILSpy or dnSpy. Game updates rename, re-sign, or restructure methods, which is the single biggest reason a Harmony mod breaks after a patch. Patch the narrowest method that does the job.
6. Build, install, test
- Build the project. With the JotunnModStub post-build step, the DLL lands in
BepInEx/pluginsautomatically; otherwise copy it there yourself. - Launch Valheim. Watch the BepInEx console / LogOutput.log for your
{PluginName} loadedline - that confirms BepInEx found and ran your plugin. - Test in-game: spawn or craft your item, trigger your patched behaviour, and check the log for errors.
7. Distribute and run on a dedicated server
Valheim mods are published on Thunderstore, and players install them with a mod manager such as r2modman. To run a mod on a dedicated server, the same DLL goes in the server's BepInEx/plugins folder. Whether clients also need it is exactly what your [NetworkCompatibility] level declares: a content mod set to EveryoneMustHaveMod must be installed on the server and every client, at matching versions, or players are rejected at join.
For getting BepInEx and mods onto your server and clients in sync, see Install Mods (r2modman sync) and BepInEx on a Valheim server; for the server itself, Dedicated Server Setup.
8. Common pitfalls
| Symptom | Cause / fix |
|---|---|
| Plugin never loads (no log line) | DLL not in BepInEx/plugins, or BepInEx itself not installed/enabled |
private game member won't compile | You are referencing the stock assembly - reference the publicized assembly_valheim instead |
| Harmony patch silently stops working after an update | Valheim renamed/re-signed the method - re-check the signature in a decompiler and update the patch |
| Players kicked: "incompatible mods" | [NetworkCompatibility] mismatch or different mod versions between server and client |
Item shows $item_evilsword as its name | Localization token not registered - add the strings via Jotunn's LocalizationManager |
| "Disabling BepInEx didn't make my server vanilla" | Disabling the loader does not unload already-installed plugins - see Verify Vanilla |
Related reading
- Install Mods on Valheim (r2modman) - syncing mods between client and server
- BepInEx on Valheim - the loader your DLL depends on
- Valheim Dedicated Server Setup - the server your mod will run on
- Official docs: Jotunn - the Valheim modding library
Host Your Modded Valheim Server with Supercraft
Supercraft runs Valheim dedicated servers with BepInEx supported, mod sync between server and clients, daily backups, and the headroom Ashlands and heavy mod lists need. Build your plugin, push it to Thunderstore, and run it on a server that stays online 24/7.