


Part of the BreakoutMods modding suite.
CIR, short for Custom Item Registry, is a Valheim modding API for developers who want to ship custom 3D item prefabs from AssetBundles without rewriting ObjectDB, ZNetScene, recipe, and multiplayer registration glue in every mod.
Repository: BreakoutMods/CIR-CustomItemRegistry
Community: BreakoutMods Discord
Support development: BreakoutMods Patreon
The library is built for BepInEx 5.x, Harmony, and Jotunn. It leans on Jotunn's PrefabManager and ItemManager for multiplayer-safe prefab and recipe registration, while exposing the original small API, the CIR 0.2 raw builder API, CIR 0.3 typed item templates, CIR 0.4 typed recipe helpers, and CIR 0.6 optional YAML/JSON item packs.
Reference CustomItemRegistry.dll from your mod project and add BepInEx dependencies:
[BepInDependency(CustomItemRegistryPlugin.PluginGuid)]
[BepInDependency(Jotunn.Main.ModGuid)]
Valheim also has a game type named CraftingStation. If your project references Valheim assemblies, add an alias for CIR's enum:
using CIRCraftingStation = ValheimCustomItemRegistry.CraftingStation;
Register a templated item from Awake:
CustomItemRegistry.Item("BM_IronLongsword")
.FromEmbeddedResource("MyMod.Assets.items", typeof(MyPlugin).Assembly, "BM_IronLongsword")
.DisplayName("$item_bm_ironlongsword")
.Description("$item_bm_ironlongsword_desc")
.Icon("BM_IronLongswordIcon")
.AsSword(sword => sword
.Slash(48f, perLevel: 6f)
.Block(24f, force: 18f, parry: 2f)
.Durability(250f, perLevel: 50f)
.Attack(stamina: 14f, force: 35f)
.Movement(-0.05f))
.Recipe(recipe => recipe
.At(CIRCraftingStation.Forge)
.StationLevel(2)
.Requires(VanillaItem.FineWood, 4)
.Requires(VanillaItem.Iron, 10)
.Requires(VanillaItem.Iron, 0, 8))
.Register();
Typed helpers remove most recipe string memorization, but raw prefab strings remain supported:
using static ValheimCustomItemRegistry.ItemRefs;
CustomItemRegistry.Item("BM_MagicBlade")
.FromBundle(assetBundlePath, "BM_MagicBladePrefab")
.AsSword(sword => sword.Slash(40f).Spirit(10f))
.Recipe(recipe => recipe
.At(CIRCraftingStation.Forge)
.Requires(VanillaItem.Silver, 10)
.Requires(Modded("com.otherauthor.valheim.magicmod", "MagicCore"), 1))
.Register();
You can still use the raw 0.2 builder when you need direct shared-data control:
CustomItemRegistry.Item("BreakoutMaterial")
.FromBundle(assetBundlePath, "BreakoutMaterialPrefab")
.Icon("BreakoutMaterialIcon")
.Gear(gear => gear.Material().StackSize(50))
.ConfigureSharedData(shared => shared.m_value = 25)
.Register();
The original API remains supported:
CustomItemRegistry.RegisterItem(
"MyCrystalSword",
Path.Combine(Path.GetDirectoryName(Info.Location), "myitems"),
"MyCrystalSwordPrefab",
new CraftingRecipe(
new List<Ingredient>
{
new Ingredient("FineWood", 8),
new Ingredient("Crystal", 12),
new Ingredient("Silver", 4)
},
"forge",
1));
By default, CIR expects your AssetBundle prefab to already contain the required Valheim item components: ItemDrop, Rigidbody, ZNetView, ZSyncTransform, and a collider. If any are missing, CIR refuses registration and lists the missing components. For simple test items, you can opt into CIR auto-preparation with .PrefabPreparation(...).
CIR can load raw item definitions from files without making YamlDotNet or Json.NET mandatory. Put item packs in:
BepInEx/config/CustomItemRegistry/packs
CIR scans that folder recursively for .yaml, .yml, and .json on startup. YAML support activates when ValheimModding-YamlDotNet is installed. JSON support activates when ValheimModding-JsonDotNET is installed. CIR does not ship those DLLs inside its own package.
Mods can also load packs from their own folder:
CustomItemRegistry.LoadItemPacksFromDirectory(
Path.Combine(Path.GetDirectoryName(Info.Location), "packs"));
Check optional parser availability with:
ItemPackParserStatus status = CustomItemRegistry.GetItemPackParserStatus();
See docs/item-packs.md for the schema, examples, and asset path rules.
CustomItemRegistry.Item(string itemName) fluent builder entrypoint.RegisterItem(string itemName, string assetBundlePath, string prefabName, CraftingRecipe recipe) legacy API.RegisterItem(CustomItemDefinition definition), TryRegisterItem(...), and RegisterItems(...).LoadItemPacks(...), LoadItemPacksFromDirectory(...), LoadItemPack(...), and GetItemPackParserStatus() for optional YAML/JSON packs.CustomItemBuilder, RecipeBuilder, GearBuilder, CustomItemDefinition, ItemRegistrationResult, and CustomItemRegistrationException.WeaponTemplateBuilder, ShieldTemplateBuilder, ArmorTemplateBuilder, BowTemplateBuilder, AmmoTemplateBuilder, ToolTemplateBuilder, FoodTemplateBuilder, and MaterialTemplateBuilder.VanillaItem, CraftingStation, ItemRef, ItemRefs, and ToPrefabName() extension methods.CraftingRecipe with ingredients, crafting station, repair station, station level, amount, enabled flag, require-only-one ingredient, and quality result multiplier.AssetBundle instances, or loaded from embedded resources with .FromEmbeddedResource(...).CustomItemBuilder.PrefabPreparation(...) opts into safe auto-preparation for simple prefabs or adjusts strict validation behavior.Chest, Legs, Helmet, and Shoulder prefabs for skinned mesh setup before registration, catching common SetupVisEquipment equip crashes early.RecipeBuilder.At(CIRCraftingStation.Forge) and RepairAt(CIRCraftingStation.Workbench) map known stations to Jotunn prefab names.RecipeBuilder.Requires(VanillaItem.Iron, 10) maps common Valheim ingredients to prefab names.RecipeBuilder.Requires(ItemRef.Modded("com.author.mod", "MagicCore"), 1) soft-links third-party mod items without compile-time references.ItemRef.Prefab("SomePrefab") and raw .Requires("SomePrefab", 1) remain available for custom or newly added prefabs..AsSword(...), .AsAxe(...), .AsMace(...), .AsSpear(...), .AsKnife(...), and .AsAtgeir(...) for melee weapons..AsBow(...) and .AsArrow(...) for ranged weapons and ammo..AsShield(...), .AsArmorChest(...), .AsArmorLegs(...), .AsHelmet(...), and .AsCape(...) for defense items..AsTool(...), .AsFood(...), and .AsMaterial(...) for common non-weapon items.Sprite.Sprite by default. Opt-in prefab preparation can allow CIR to convert a Texture2D with the same name into a runtime sprite..ConfigureSharedData(...) escape hatch for advanced ItemDrop.ItemData.SharedData edits.BepInEx/plugins.ItemDrop, Rigidbody, ZNetView, ZSyncTransform, and collider components by default.ItemDrop, Rigidbody, ZNetView, and ZSyncTransform when explicitly enabled.PrefabManager for multiplayer-safe ZNetScene registration.ItemManager.ObjectDB.CopyOtherDB and ZNetScene.Awake to flush items into live databases when Valheim creates or copies them.For production items, build the Unity prefab like a Valheim item and include ItemDrop, Rigidbody, ZNetView, ZSyncTransform, and a collider.
For quick test items, the Unity prefab can be very small if you explicitly opt into auto-preparation:
AssetBundle
MyItemPrefab
visible mesh/model
collider recommended
MyItemIcon Sprite or Texture2D
CIR will add the common Valheim item scripts at registration time only when .PrefabPreparation(...) enables it. Use templates such as .AsMaterial(...), .AsArmorChest(...), or .AsSword(...) so CIR knows which item type and stats to apply.
CIR does not repair Unity Missing Script references, does not choose collider shape/size, and does not guess vanilla asset references. If you use vanilla assets, use Jotunn JVLmock_... references.
Wearable armor is stricter than normal items. .AsArmorChest(...), .AsArmorLegs(...), .AsHelmet(...), and .AsCape(...) need a real Valheim-style wearable visual in the prefab: at least one SkinnedMeshRenderer with a mesh, rootBone, and valid bones. Chest, legs, and cape prefabs should usually follow the vanilla attach_skin... hierarchy. A static mesh plus icon is fine for materials, but not for equipped armor.
If imported armor equips with broken visuals or null references even though the skinned renderer exists, base the prefab on a vanilla armor item and consider calling Jotunn BoneReorder.ApplyOnEquipmentChanged() from your plugin.
Advanced mods with their own custom equipment visual pipeline can disable only this check with .PrefabPreparation(prep => prep.ValidateWearableVisuals(false)).
Example opt-in for a simple mesh prefab:
CustomItemRegistry.Item("SimpleItem")
.FromBundle(assetBundlePath, "SimpleItemPrefab")
.PrefabPreparation(prep => prep
.AutoAddItemDrop()
.AutoAddPhysics()
.WarnOnMissingCollider()
.AllowTextureIconFallback())
.AsMaterial()
.Register();
The src/ExampleCustomItemPlugin project shows template, raw builder, definition, try-register, validation harness, and legacy API usage. Its sample AssetBundle and prefab names are placeholders, so replace them with real assets before shipping.
CIR-CustomItemRegistry/
CIR-CustomItemRegistry.sln
build.ps1
docs/
recipes.md
item-packs.md
templates.md
roadmap/
developer-helper-api.md
src/
CustomItemRegistry/
API/ Public API contracts and registration facade
Builders/ Fluent item, recipe, and gear builders
Helpers/ Vanilla item, station, and modded item refs
ItemPacks/ Optional YAML/JSON pack parsers and DTO mapping
Templates/ Typed Valheim item template builders
Patches/ Harmony timing patches
Plugin/ BepInEx plugin entrypoint
ExampleCustomItemPlugin/
Examples/ Developer-facing usage examples
Testing/ Lightweight compile/validation harnesses
The public namespace and assembly identity stay stable even though the source files are grouped by responsibility.
If you're using a mod manager, you can likely ignore this section.
CustomItemRegistry.dll into BepInEx/plugins/CustomItemRegistry.BepInEx/plugins.This repo expects to live under a Valheim install like:
Valheim dedicated server/
BepInEx/
valheim_server_Data/
Modding/
CIR-CustomItemRegistry/
Build with:
.\build.ps1 -Configuration Release
Debug builds copy the API DLL into BepInEx/plugins/CustomItemRegistry and the example DLL into BepInEx/plugins/ExampleCustomItemPlugin.
VanillaItem and CraftingStation for common ingredients and stations. Use raw strings for uncommon or newly added prefabs.ItemRef.Modded("other.mod.guid", "PrefabName") when depending on a third-party item. CIR logs that source mod GUID if the prefab is missing.piece_workbench, forge, and piece_cauldron. Passing null or an empty string makes the recipe craftable without a station.ItemDrop shared data, pass a direct Sprite, or call .Icon("SpriteAssetName") for craftable items..Icon("AssetName") can also use a Texture2D with the same name if no Sprite exists when AllowTextureIconFallback() is enabled..Requires("Bronze", 0, 4) when an ingredient should only be consumed by upgrades..FromEmbeddedResource("Namespace.BundleName", typeof(MyPlugin).Assembly, "PrefabName")..Gear(...) or .ConfigureSharedData(...) only for unusual behavior.Please open issues with the Valheim version, BepInEx version, Jotunn version, the item prefab name, and the relevant BepInEx log lines. Pull requests that keep the public API small and improve interop with Jotunn are welcome.
See CHANGELOG.md.