RLS Studios
ProjectsPatreonCommunityDocsAbout
Join Patreon
BeamNG Modding Docs

Guides

Lua Classes & MetatablesLua Coding StandardsProven GE PatternsAnti-Patterns to AvoidOverriding Lua ModulesCleaning Up AI-Generated CodeError Handling & Defensive CodingPerformance OptimizationLua Modding FAQ

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

GuidesLua

Overriding Lua Modules

How to safely override and extend existing Lua module functions without breaking other mods.

When modding BeamNG, you often need to change or extend the behavior of an existing module. The golden rule: never replace the original file. Instead, override individual functions at runtime so your changes stack with other mods.

This guide covers the function wrapping technique — the standard way to extend existing modules while keeping compatibility with the base game and other mods.


Why Not Just Replace the File?

If your mod includes a file at the same path as a vanilla file (e.g. lua/ge/extensions/core/something.lua), your version completely replaces the original. This causes problems:

  • Other mods break — if two mods both replace the same file, only one wins
  • Game updates break your mod — when BeamNG updates that file, your old copy is missing the changes
  • You lose all original functionality unless you manually copy it into your version

The repository will also reject mods that overwrite default game files.


The Function Wrapping Pattern

Instead of replacing a file, create your own extension that wraps the target function:

-- lua/ge/extensions/auto/myMod_overrides.lua
local M = {}

local originalFunc = nil

local function onExtensionLoaded()
  -- Get a reference to the module you want to modify
  local targetModule = extensions.gameplay_traffic

  -- Store the original function
  originalFunc = targetModule.setupTraffic

  -- Replace with your wrapper
  targetModule.setupTraffic = function(...)
    -- Your logic BEFORE the original runs
    log('I', 'myMod', 'setupTraffic called with custom wrapper')

    -- Call the original function with all its arguments
    local result = originalFunc(...)

    -- Your logic AFTER the original runs
    -- e.g. modify the result, add extra behavior, etc.

    return result
  end
end

M.onExtensionLoaded = onExtensionLoaded
return M

Why This Works

  1. You store a reference to the original function before overwriting it
  2. Your wrapper calls the original, so all vanilla behavior is preserved
  3. If another mod wraps the same function, it wraps your wrapper — the calls chain together
  4. The original file is never modified

Wrapping Multiple Functions

For modules where you need to override several functions:

local M = {}

local origSetup = nil
local origCleanup = nil

local function onExtensionLoaded()
  local target = extensions.gameplay_traffic

  origSetup = target.setupTraffic
  target.setupTraffic = function(...)
    log('I', 'myMod', 'Before setup')
    local result = origSetup(...)
    log('I', 'myMod', 'After setup')
    return result
  end

  origCleanup = target.cleanup
  target.cleanup = function(...)
    log('I', 'myMod', 'Before cleanup')
    local result = origCleanup(...)
    -- Add your cleanup here
    return result
  end
end

M.onExtensionLoaded = onExtensionLoaded
return M

Choosing When to Call the Original

Where you place the call to the original function matters — it depends on what you're trying to achieve:

Run your logic after the original

Call the original first, then do your thing. Useful when you need the original to set things up before you modify the result or add behavior on top:

target.setupTraffic = function(...)
  local result = origFunc(...)  -- Original runs first
  -- Now modify result or do additional work
  return result
end

Run your logic before the original

Do your thing first, then let the original run. Useful for validation, logging, or modifying arguments before they reach the original:

target.setupTraffic = function(arg1, ...)
  -- Modify arguments or set up state
  arg1 = sanitize(arg1)
  return origFunc(arg1, ...)  -- Original runs after
end

Run your logic around the original

Wrap both sides — set something up before, react to the result after:

target.setupTraffic = function(...)
  prepareMyStuff()              -- Before
  local result = origFunc(...)  -- Original
  adjustBasedOn(result)         -- After
  return result
end

Which approach you choose depends entirely on why you need the override. There's no single right answer.


Limitations

Function wrapping gives you access to a module's public interface — the functions and values it exposes on its module table (M). However, you cannot access local variables or local functions inside the original module. Anything declared with local in the original file is invisible to your wrapper.

If the behavior you need to change is driven by a local variable or a local helper function, wrapping won't help. In those cases, your options are limited — you may need to request an API change from the module author, or find a different approach entirely.


Conditionally Overriding

Sometimes you only want to modify behavior under certain conditions. The wrapper pattern handles this naturally:

local origFunc = nil

local function onExtensionLoaded()
  local target = extensions.some_module

  origFunc = target.doSomething
  target.doSomething = function(arg1, arg2, ...)
    -- Only modify behavior when your condition is met
    if someCondition then
      -- Your custom behavior
      return myCustomResult
    end

    -- Otherwise, fall through to the original
    return origFunc(arg1, arg2, ...)
  end
end

Timing: When to Apply Overrides

Use onExtensionLoaded to apply your wraps. This hook fires after your extension is loaded, and the target modules will already be available through the extensions table.

If you need to target a module that loads later, you can wrap in a different hook:

local applied = false

local function onUpdate(dtReal, dtSim, dtRaw)
  if not applied and extensions.some_late_module then
    -- Apply wraps here
    applied = true
  end
end

Multiple Mods Stacking

The beauty of this pattern is that it chains. If three mods all wrap gameplay_traffic.setupTraffic:

  1. Mod A stores the original, replaces with Wrapper A
  2. Mod B stores Wrapper A (thinks it's the original), replaces with Wrapper B
  3. Mod C stores Wrapper B, replaces with Wrapper C

When setupTraffic is called: Wrapper C → Wrapper B → Wrapper A → Original

Each mod's additions run without knowing about the others. No conflicts, no overwrites.


Common Mistakes

Forgetting to call the original

-- BAD: Original behavior is completely lost
target.doSomething = function(...)
  return myNewBehavior()
end

-- GOOD: Original behavior preserved
target.doSomething = function(...)
  local result = origFunc(...)
  -- Add your stuff
  return result
end

Wrapping too early

If you wrap in your module's top-level scope (outside a function), the target module might not be loaded yet. Always wrap inside onExtensionLoaded or later.

Not passing arguments through

-- BAD: Drops any arguments the original expects
target.doSomething = function()
  return origFunc()
end

-- GOOD: Passes all arguments through
target.doSomething = function(...)
  return origFunc(...)
end

See Also

  • Extension Patterns — Common Lua patterns in BeamNG
  • Creating a GE Extension — How to build your own extension
  • Publishing Your Mod — Repository rules including the no-overwrite policy
  • Module System — How BeamNG's module system works

Anti-Patterns to Avoid

Common BeamNG modding mistakes that cause crashes, data loss, and silent failures — with correct alternatives for each.

Cleaning Up AI-Generated Code

How to identify and fix common AI coding patterns — excessive guards, restating comments, unnecessary boilerplate, and other slop that hurts readability.

On this page

Why Not Just Replace the File?The Function Wrapping PatternWhy This WorksWrapping Multiple FunctionsChoosing When to Call the OriginalRun your logic after the originalRun your logic before the originalRun your logic around the originalLimitationsConditionally OverridingTiming: When to Apply OverridesMultiple Mods StackingCommon MistakesForgetting to call the originalWrapping too earlyNot passing arguments throughSee Also