Loading Extension Folders
How to automatically load all extensions from a folder with proper namespacing — essential for mods with multiple features.
As your mod grows beyond a single file, manually loading each extension becomes tedious and error-prone. The module loading system lets one root extension automatically discover and load every .lua file in a folder — keeping your mod organized and extensible.
The Pattern
lua/ge/extensions/
mymod/
init.lua -- Root extension (loads everything)
vehicles.lua -- Sub-module
economy.lua -- Sub-module
ui.lua -- Sub-module
helpers/
math.lua -- Nested sub-module
strings.lua -- Nested sub-moduleThe root extension (init.lua) loads the entire mymod/ folder. Modders only need to call extensions.load('mymod_init') and everything else loads automatically.
Loading a Directory
extensions.loadModulesInDirectory(directory, excludeSubdirectories)
Scans a directory for .lua files and loads each one as an extension:
-- Load all .lua files in a directory (recursively)
extensions.loadModulesInDirectory("lua/ge/extensions/mymod")This finds every .lua file in the directory (including subdirectories), loads it, and registers it as an extension. Each file is loaded at the global root, meaning the extension name is just the filename without the path.
Excluding Subdirectories
You can exclude specific subdirectories:
-- Load everything except the "internal" and "debug" folders
extensions.loadModulesInDirectory("lua/ge/extensions/mymod", {"internal", "debug"})The exclude list matches against the file path, so any file containing that substring in its path is skipped.
Loading with a Namespace
extensions.loadAtRoot(extPath, rootName)
Loads a single file as an extension with a custom namespace prefix:
-- Loads lua/ge/extensions/mymod/economy.lua as "mymod_economy"
extensions.loadAtRoot("lua/ge/extensions/mymod/economy", "mymod")The resulting extension name is rootName_filename. This is how you keep your extensions namespaced to avoid conflicts with other mods.
Rules:
rootNamecannot contain underscoresextPathmust be a file path (contain/)- The loaded extension is automatically marked as manually loaded (persistent)
If you pass an empty string "" as rootName, the extension name is just the filename (no prefix). This is what loadModulesInDirectory uses internally.
Complete Example: Root Extension
Here's a complete root extension that loads all sub-modules from its folder:
-- lua/ge/extensions/mymod/init.lua
-- Extension name: mymod_init
local M = {}
local modulePath = "lua/ge/extensions/mymod"
local function onExtensionLoaded()
log('I', 'mymod', 'Loading mymod modules...')
local files = FS:findFiles(modulePath, "*.lua", -1, true, false)
for _, file in ipairs(files) do
if not file:find("init.lua$") then
local extPath = file:sub(1, -5)
local extName = extensions.loadAtRoot(extPath, "mymod")
if extName then
log('I', 'mymod', ' Loaded: ' .. extName)
end
end
end
log('I', 'mymod', 'All modules loaded.')
end
local function onExtensionUnloaded()
log('I', 'mymod', 'Unloading mymod...')
end
M.onExtensionLoaded = onExtensionLoaded
M.onExtensionUnloaded = onExtensionUnloaded
return MSub-module Example
-- lua/ge/extensions/mymod/economy.lua
-- Will be loaded as extension: mymod_economy
local M = {}
local playerMoney = 0
local function addMoney(amount)
playerMoney = playerMoney + amount
log('I', 'mymod.economy', 'Added $' .. amount .. ', total: $' .. playerMoney)
guihooks.trigger('MyModMoneyChanged', {money = playerMoney})
end
local function getMoney()
return playerMoney
end
local function onExtensionLoaded()
log('I', 'mymod.economy', 'Economy module loaded')
end
M.addMoney = addMoney
M.getMoney = getMoney
M.onExtensionLoaded = onExtensionLoaded
return MUsing Sub-modules from Other Code
Once loaded, sub-modules are accessible as globals:
-- From any other extension or Lua code:
if mymod_economy then
mymod_economy.addMoney(500)
local balance = mymod_economy.getMoney()
end
-- Or use extensions.hook to broadcast to all your modules:
extensions.hook('onMyModEvent', eventData)How Vanilla Uses This
Vehicle Extensions
The vehicle Lua system uses loadModulesInDirectory to load all vehicle extensions:
-- From vehicle/main.lua
extensions.loadModulesInDirectory(path .. "/lua", {"controller", "powertrain", "energyStorage"})This loads all vehicle Lua files except controllers, powertrains, and energy storage (which are loaded separately by the powertrain system).
Scenario Extensions
Scenarios use loadAtRoot to load scenario-specific extensions with the scenario namespace:
-- From scenario/scenarios.lua
local extName, m = extensions.loadAtRoot(moduleFullPath, "scenario")
-- Result: extension named "scenario_myScenarioModule"This keeps scenario extensions namespaced under scenario_* so they don't conflict with other extensions.
Best Practices
-
Always use a namespace - Pass your mod name as
rootNametoloadAtRootso your extensions don't clash with other mods or vanilla. -
Register in your mod script - Use
setExtensionUnloadMode("mymod_init", "manual")in yourscripts/mymod/modScript.lua, not inside the extension. Sub-modules loaded vialoadAtRootare automatically marked as manually loaded. -
Skip your own init file - When scanning the directory, filter out the root extension itself to avoid recursive loading.
-
Use
FS:findFilesfor discovery - This is the engine's virtual filesystem API that works with both unpacked folders and ZIP mods. -
Handle load order - If sub-modules depend on each other, either use
extensions.load()for the dependency first, or useonExtensionLoadedto defer initialization until everything is ready. -
Check before calling - Always nil-check a sub-module before calling its functions:
if mymod_economy then mymod_economy.addMoney(100) end
See Also
- Mod Scripts - Registering extensions for persistence
- Creating Extensions - Extension reference
- Architecture - How extensions fit together
Quick Reference
| Function | Purpose |
|---|---|
extensions.loadModulesInDirectory(dir) | Load all .lua files in a directory |
extensions.loadModulesInDirectory(dir, exclude) | Load directory, skip listed subdirectories |
extensions.loadAtRoot(path, namespace) | Load one file with a namespace prefix |
FS:findFiles(dir, pattern, depth, files, dirs) | Find files in the virtual filesystem |
setExtensionUnloadMode("name", "manual") | Keep extension loaded (use in modScript.lua) |