How Mods Are Loaded
How BeamNG discovers, mounts, and activates mods — ZIP structure, virtual filesystem overlay, and the mod manager API.
Understanding how mods are discovered and mounted into BeamNG's virtual filesystem is crucial for packaging your mod for distribution. This guide covers the full lifecycle — from dropping a ZIP into the mods folder to your files being available in-game.
Mod Directory Structure
Mods live in the user's mods/ folder. Two formats are supported:
1. ZIP Mods (Standard Distribution)
mods/
mymod.zipThe ZIP contains files that overlay the game's virtual filesystem. When mounted, files inside the ZIP shadow (override) base game files at the same path.
2. Unpacked Mods (Development)
mods/unpacked/
mymod.zip/ # Directory named like a zip
vehicles/
lua/
...Unpacked mods are directories inside mods/unpacked/. They behave identically to ZIP mods but are easier to edit during development.
mod_info - Repository Metadata
Mods downloaded from the repository contain a mod_info/ directory with metadata:
mod_info/
<MOD_ID>/
info.json # Mod metadata, hashes, attachmentsThe info.json contains:
- Mod name, description, author
- File hashes for integrity verification
- Attachment references (thumbnails, images)
- Icon path
-- How modmanager reads mod_info (from modmanager.lua:335)
local modID = string.match(tostring(v2), '^/?mod_info/([0-9a-zA-Z]*)/info%.json')
if modID then
modID = modID:upper()
mods[modname].modID = modID
mods[modname].modInfoPath = '/mod_info/'..modID..'/'
local jsonContent = zip:readFileEntryByIdx(k2)
mods[modname].modData = jsonDecode(jsonContent, ...)
endMod Type Detection
The mod manager auto-detects mod type based on file contents:
| Type | Detection Rule | Mount Point |
|---|---|---|
vehicle | Contains .jbeam files and vehicles/ | vehicles/ |
terrain | Contains .mis or .level.json files | levels/ |
app | Contains ui/ directory | (root) |
scenario | Contains scenarios/ (but no .mis) | (root) |
sound | Contains .sbeam (but no .jbeam/.mis) | (root) |
If the mod's internal folder structure doesn't match the expected mount point (e.g., vehicle files not inside vehicles/), the mod manager auto-remounts to the correct path.
File System Overlay
BeamNG uses a virtual filesystem (VFS) that layers multiple sources:
Priority (highest first):
1. mods/unpacked/ (development mods)
2. mods/*.zip (user mods)
3. Base game files (shipped with game)Key principle: When a mod contains a file at the same path as a base game file, the mod's version takes priority. This is how mods override game content without modifying original files.
-- Check where a file actually comes from (modmanager.lua:113)
local function getModFromPath(vfsPath)
local originArchivePath = FS:getOriginArchivePathRelative(vfsPath)
-- Returns the mod entry that provides this file
endMounting and Unmounting
-- Mount a mod into the VFS (modmanager.lua:86-92)
local function mountEntry(filename, mountPoint)
extensions.hook('onBeforeMountEntry', filename, mountPoint)
local mountList = {}
addMountEntryToList(mountList, filename, mountPoint)
FS:mountList(mountList)
endMod Lifecycle
1. Discovery
On startup (or UI ready), core_modmanager scans:
mods/*.zip- packaged modsmods/unpacked/*/- development mods
2. Database
Mod state is persisted in mods/db.json:
-- modmanager.lua:9
local persistencyfile = 'mods/db.json'
-- Contains: { header = dbHeader, mods = mods }3. Activation
Each mod has an active flag. Only active mods are mounted into the VFS.
4. State Change Notification
When mods change state:
-- modmanager.lua:58-68
guihooks.trigger('ModManagerModsChanged', mods)
guihooks.trigger('ModManagerVehiclesChanged', vehicles)
extensions.hook('onModManagerStateChanged')Creating a Mod for Distribution
Minimum Structure (Vehicle Mod)
mymod.zip/
vehicles/
mycar/
mycar.jbeam
mycar.dae # 3D model
...Minimum Structure (Lua Extension Mod)
mymod.zip/
lua/ge/extensions/
mymod_myextension.luaMinimum Structure (UI App Mod)
mymod.zip/
ui/modules/apps/
myapp/
app.js
app.htmlWith Metadata
mymod.zip/
mod_info/
ABCD1234/
info.json
vehicles/
mycar/
...Safe Mode
In safe mode, the mod database is not saved to disk:
-- modmanager.lua:65
if not isSafeMode() then
jsonWriteFile(persistencyfile, { header = dbHeader, mods = mods }, true)
endKey API (core_modmanager)
| Function | Description |
|---|---|
core_modmanager.activateMod(modname) | Enable a mod |
core_modmanager.deactivateMod(modname) | Disable a mod |
core_modmanager.deleteMod(modname) | Remove a mod |
core_modmanager.getModFromPath(vfsPath) | Find which mod provides a file |
core_modmanager.modIsUnpacked(modname) | Check if mod is in unpacked/ |
Hooks
| Hook | Args | When |
|---|---|---|
onModManagerStateChanged | none | Any mod activated/deactivated/installed |
onBeforeMountEntry | (filename, mountPoint) | Before a mod is mounted into VFS |
See Also
- Getting Started - First mod setup
- Mod Scripts - Extension persistence via modScript.lua
- Architecture - Extension system reference
- Boot Sequence - Startup order
GE Cross-VM Communication
How GE communicates with Vehicle Engines and the UI — bridge methods, hooks, and patterns for cross-context data flow.
Inter-VM Communication (GE ↔ VE ↔ UI)
Complete guide to cross-VM communication — GE to VE commands, VE to GE callbacks, peer vehicle messaging, UI events, and the electrics data bus.