Build a Project Zomboid Mod: From mod.info to Lua Scripting
Project Zomboid has one of the deepest mod ecosystems in survival gaming, and the barrier to entry is low: items and recipes are plain text files, and behaviour is written in Lua. You do not compile anything. This guide walks from a two-file mod that adds a custom item to event-driven Lua scripting, then on to distribution and running your mod on a dedicated server. It covers both Build 41 (the stable branch most multiplayer servers still run) and the new Build 42 folder layout, and flags where the two differ.
Two kinds of modding, two skill levels. Adding items and recipes is done in plain .txt script files and needs little or no programming. Changing how the game behaves - context menus, timed actions, spawning, custom systems - is done in Lua. Most real mods use both. This guide does both, starting from the easy half.
What you'll build
- Where mods live + the folder structure (Build 41 vs Build 42)
- mod.info - the manifest that makes your mod show up
- Your first content: a custom script item
- The Lua layer - the Events system and load order
- Recipes and the Build 42 crafting change
- Supporting Build 41 and Build 42 from one mod
- Distribute: Workshop and local
- Run it on a dedicated server
- Common pitfalls
1. Where mods live and how they are laid out
For local development, a mod is a folder inside your user data directory's mods folder - Zomboid/mods/ - so the game detects it without going through Steam Workshop at all. The internal layout is where Build 41 and Build 42 diverge.
Build 41 (flat layout)
A single-build mod has mod.info at its root and all content under media/:
MyFirstMod/
mod.info
poster.png
media/
lua/
shared/ first to load, shared by client and server
client/ loads after shared: UI, context menus, timed actions
server/ loads only when a game starts: spawning, farming, weather
scripts/ .txt files: item, recipe, vehicle definitions
textures/ PNG icons (item icons are prefixed Item_)
Build 42 (versioned layout)
Build 42 lets one mod ship content for more than one game version. The mod is split into version folders, each with its own mod.info, plus a common/ folder for content shared across builds:
MyFirstMod/
common/
media/ content that works on every build (lua, scripts, textures)
42/
mod.info the Build 42 manifest
poster.png
media/ Build 42 only content (optional)
41/ optional, only if you also support Build 41
mod.info
poster.png
media/
The media/ sub-tree (lua/shared, lua/client, lua/server, scripts, textures) is the same inside common/ and inside each version folder. Put anything build-agnostic in common/media/; put build-specific content under 42/media/ or 41/media/.
Which build should you target? If your server runs Build 41 stable, mod for 41 and use the flat layout. If you are modding Build 42, use the versioned layout. The common/ + 41/ + 42/ split exists so a single Workshop upload can serve both audiences - see section 6.
2. mod.info
This text file is what makes the game list your mod. Minimum viable file:
name=My First Mod
id=MyFirstMod
description=Adds a custom item and some Lua behaviour.
poster=poster.png
author=You
| Field | What it is |
|---|---|
name | The display name shown in the in-game mods list |
id | The unique identifier. This is the value that goes in a server's Mods= line and in another mod's dependency list. It is not the same as the Steam Workshop ID. |
description | Short text shown in the mods menu |
poster | Preview image filename (a PNG beside mod.info) |
author | Your name or handle (optional) |
require | Optional - the id of another mod yours depends on, so load order is enforced |
Remember the id. The single most common Project Zomboid server problem is a mismatch between the Mod ID a server lists and the actual id in mod.info.
3. Your first content: a custom item
Items, recipes, and vehicles are defined in .txt files under media/scripts/. Everything lives inside a module block, and you import Base to build on the vanilla game. Create media/scripts/items.txt:
module MyFirstMod
{
imports
{
Base
}
item LuckyCoin
{
Type = Normal,
DisplayName = Lucky Coin,
Icon = LuckyCoin,
Weight = 0.1,
}
}
Two things to internalise:
- The item's full game name becomes
module.item- hereMyFirstMod.LuckyCoin. That fully-qualified name is what you reference everywhere else (recipes, Lua, theadditemdebug command). Icon = LuckyCoinpoints at a texture. Drop a 32x32-ish PNG atmedia/textures/Item_LuckyCoin.png- the engine prependsItem_to the icon name automatically.
Test it: launch the game, enable "My First Mod" in the Mods menu, start a sandbox game with debug mode on, and spawn the item with the admin/debug item panel (or the console: additem "MyFirstMod.LuckyCoin"). If it appears with your icon and name, the script and texture are wired correctly.
4. The Lua layer: the Events system
Static scripts define things. To give your mod behaviour you write Lua, and almost all Project Zomboid Lua is driven by an event/callback system: Java fires events, and your Lua functions register as callbacks. The pattern is always the same - hand a function reference to Events.EventName.Add:
-- media/lua/client/MyFirstMod.lua
local function onGameStart()
print("MyFirstMod loaded")
end
Events.OnGameStart.Add(onGameStart) -- pass the function, no () after it
The classic beginner bug: do not write Events.OnGameStart.Add(onGameStart()). The parentheses call your function immediately and register its return value (usually nil) instead of the function itself. Pass the bare reference.
Useful events to know: OnGameStart (once, when a game begins), OnPlayerUpdate (every tick per player - powerful but hot, keep it cheap), OnKeyPressed, OnFillInventoryObjectContextMenu (add right-click menu entries). You can also remove a callback with Events.EventName.Remove(fn) if you kept the reference, and define your own events with LuaEventManager.AddEvent("MyEvent").
Load order matters
The three lua/ folders load in a fixed sequence, and files load alphabetically within each:
media/lua/shared/- loaded first; logic used by both client and servermedia/lua/client/- after shared; UI, context menus, timed actionsmedia/lua/server/- loaded only when a game actually starts; spawning, farming, weather, anything authoritative
Put a function where it needs to run. UI code in client, world-authority code in server, shared helpers and definitions in shared. Getting this wrong is why a mod "works in single-player but breaks in multiplayer".
5. Recipes and the Build 42 crafting change
In Build 41, recipes are also script blocks inside your module. Note the syntax is different from items - properties use key:value, and ingredients are listed first:
recipe Open Box of Nails
{
NailsBox,
Result:Nails=20,
Sound:PutItemInBag,
Time:5.0,
}
Build 42 replaced the crafting system. The deep crafting overhaul in Build 42 changed how recipes are defined - the Build 41 recipe block above does not carry over unchanged. If you are modding Build 42, define crafting with the new system rather than the old recipe format, and verify the current schema against the in-game vanilla scripts (the base game's own media/scripts are the most reliable reference for the build you are on). Items and Lua, by contrast, work the same way on both builds - which is why this guide leads with them.
6. Supporting Build 41 and Build 42 from one mod
This is the whole point of the Build 42 versioned layout. Put everything that is identical on both builds - your Lua, your item scripts, your textures - in common/media/. Put each build's manifest in its own version folder (41/mod.info and 42/mod.info), along with any content that only works on that build under 41/media/ or 42/media/. The game loads common/ plus the folder matching the running build, so one upload serves both audiences without forking the whole mod.
7. Distribute: Workshop and local
Two ways to ship:
- Local / private: the mod folder in
Zomboid/mods/is already playable, and you can zip and hand that folder to anyone - they drop it in the same place. - Steam Workshop: add a
workshop.txtdescribing the Workshop item, then use the game's built-in Workshop upload tool (Main Menu > Workshop). For Build 42 the mod is packaged under aContents/mods/<id>/structure inside the Workshop item. Publishing gives you a Workshop ID (the number in the item URL), which is separate from your mod'sid- you will need both for servers.
8. Run your mod on a dedicated server
A mod that changes the world is part of the save, so the server and every client must run the same mods. On a Project Zomboid server you declare mods in two startup-config lines:
WorkshopItems=- the comma-separated Workshop IDs (the URL numbers), so the server downloads them.Mods=- the comma-separated Mod IDs (theidfrom eachmod.info), so the server actually loads them.
Order matters: list any dependency before the mod that needs it. Because the two lists use different identifiers, a mismatch is the number-one cause of "mods installed but not loading". For local or unpublished mods, copy the mod folder to the server's mods directory and add its id to Mods=. Full walkthroughs: Add Mods to a Project Zomboid Server, Collection to mod list in 3 minutes, and Mods installed but not loading.
On Linux servers, mod and file names are case-sensitive, so MyFirstMod and myfirstmod are different mods - a frequent reason a mod that worked on a Windows desktop fails on a Linux host.
9. Common pitfalls
| Symptom | Cause / fix |
|---|---|
| Mod not in the in-game list | Wrong folder location, or mod.info missing/misnamed; on B42 check the version folder has its own mod.info |
| Item spawns as a missing-texture box | Icon PNG missing or misnamed - it must be media/textures/Item_<Icon>.png |
| Lua "function" never fires | You wrote Add(fn()) with parentheses - pass the bare reference Add(fn) |
| Works solo, breaks in multiplayer | Server-authority code placed in client/ (or vice versa); move it to the correct lua/ folder |
| B41 recipe does nothing on B42 | Build 42 changed the crafting system; the old recipe block is not used - rewrite for the new system |
| "Mods installed but not loading" on a server | Mods= Mod ID does not match the id in mod.info, or dependency listed after its dependent |
| Mod fails on a Linux server only | Case mismatch between the folder/id and what the server lists |
Related reading
- Add Mods to a Project Zomboid Server - the WorkshopItems / Mods lines in detail
- Mods Installed but Not Loading - debugging Mod ID mismatches
- Project Zomboid Dedicated Server Setup - the server your mod will run on
- Community: FWolfe's Zomboid Modding Guide and PZwiki Modding
Host Your Modded Project Zomboid Server with Supercraft
Supercraft runs Project Zomboid dedicated servers (Build 41 and Build 42) with Workshop mod sync, the Mods= and WorkshopItems= lines managed from the panel, daily backups, and the RAM headroom Build 42 needs. Build your mod, push it to the Workshop, and run it on a server that stays online 24/7.