RLS Studios
ProjectsPatreonCommunityDocsAbout
Join Patreon
BeamNG Modding Docs

Guides

Your First BeamNG ModMod Scripts (modScript.lua)Creating a GE ExtensionLoading Extension Folders

Reference

UI

Resources

BeamNG Game Engine Lua Cheat SheetGE Developer RecipesMCP Server Setup

// RLS.STUDIOS=true

Premium Mods for BeamNG.drive. Career systems, custom vehicles, and immersive gameplay experiences.

Index

HomeProjectsPatreon

Socials

DiscordPatreon (RLS)Patreon (Vehicles)

© 2026 RLS Studios. All rights reserved.

Modding since 2024

GuidesGetting Started

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-module

The 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:

  • rootName cannot contain underscores
  • extPath must 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 M

Sub-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 M

Using 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

  1. Always use a namespace - Pass your mod name as rootName to loadAtRoot so your extensions don't clash with other mods or vanilla.

  2. Register in your mod script - Use setExtensionUnloadMode("mymod_init", "manual") in your scripts/mymod/modScript.lua, not inside the extension. Sub-modules loaded via loadAtRoot are automatically marked as manually loaded.

  3. Skip your own init file - When scanning the directory, filter out the root extension itself to avoid recursive loading.

  4. Use FS:findFiles for discovery - This is the engine's virtual filesystem API that works with both unpacked folders and ZIP mods.

  5. Handle load order - If sub-modules depend on each other, either use extensions.load() for the dependency first, or use onExtensionLoaded to defer initialization until everything is ready.

  6. 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

FunctionPurpose
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)

Creating a GE Extension

Step-by-step guide to creating a GE extension — file naming, lifecycle hooks, dependencies, and the full loading sequence.

GE Extension System

Complete reference for the Game Engine extension system — loading, unloading, hooks, dependencies, and creating custom extensions.

On this page

The PatternLoading a Directoryextensions.loadModulesInDirectory(directory, excludeSubdirectories)Excluding SubdirectoriesLoading with a Namespaceextensions.loadAtRoot(extPath, rootName)Complete Example: Root ExtensionSub-module ExampleUsing Sub-modules from Other CodeHow Vanilla Uses ThisVehicle ExtensionsScenario ExtensionsBest PracticesSee AlsoQuick Reference