RLS Studios
ProjectsPatreonCommunityDocsAbout
Join Patreon
BeamNG Modding Docs

Guides

Vehicle Control from GEVE Architecture Quick ReferenceVehicle Scripting PatternsVehicle Lua Cheat Sheet62 Vehicle Scripting RecipesVehicle Modding GuideUnderstanding Vehicle Damage Systems

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

GuidesVehicle

Vehicle Modding Guide

How to add custom logic to vehicles safely — extensions vs controllers, the electrics data bus, powertrain API, safe overrides, and UI visibility.

Adding custom logic to vehicles is where BeamNG modding gets powerful — and where things can go wrong fast. This guide shows you the right way to modify vehicle behavior without breaking other mods or the base game.

1. Extension vs. Controller

  • Extension: Best for modular features that should work on many vehicles (e.g., a "drift mode" script or "telemetry logger"). They load automatically if placed in lua/vehicle/extensions/auto/.
  • Controller: Best for vehicle-specific hardware logic (e.g., a specific engine management system or complex animation). They are defined in the JBeam.

2. Creating a Basic Extension

Create a file at lua/vehicle/extensions/myAwesomeMod.lua:

local M = {}

-- Called once when vehicle is loaded
local function onInit()
    print("My Awesome Mod Initialized!")
end

-- Called every graphics frame (UI rate)
local function updateGFX(dt)
    -- UI logic here
end

-- Called every physics step (2000Hz)
-- Use this for torque and forces!
local function updateFixedStep(dt)
    -- Physics logic here
end

-- Hook exports
M.onInit = onInit
M.updateGFX = updateGFX
M.updateFixedStep = updateFixedStep

return M

3. Modifying Existing Systems (The Safe Way)

When modding, you should avoid modifying the core BeamNG files directly. Instead, build your logic in an extension and interact with other systems using the Data Bus or the Powertrain API.

A. The Data Bus (electrics.values)

The electrics.values table is the "shared memory" of the vehicle. It's where the engine, dashboard, and lights all store and read data.

How to use it:

  • Reading Data: To check the vehicle's state, just read from the table.
    local currentRPM = electrics.values.rpm or 0
    if currentRPM > 5000 then
        -- Do something at high RPM
    end
  • Injecting/Overriding Data: You can write your own values into this table to trigger other systems (like dash lights or props) or override existing ones.
    local function updateGFX(dt)
        -- Force the 'Check Engine' light on
        electrics.values.checkengine = 1
        
        -- Create a custom signal for a JBeam prop to use
        electrics.values.my_mod_needle = math.sin(os.clock())
    end
  • Why use it? It decouples your script from other systems. Your script doesn't need to know how the dashboard works; it just needs to set a value that the dashboard is programmed to watch.

B. The Powertrain API

If you want to change how the vehicle moves (add power, lock wheels, change gears), you use the powertrain module to find and control physical devices.

Adding Power (Architectural Solution)

Don't hijack temporary properties or other systems. Instead, modify the engine's base torque curve during initialization. This is standalone, non-destructive, and visible in UI tools.

local function onInit()
    local eng = powertrain.getDevice("mainEngine")
    if eng and eng.torqueCurve then
        -- Add +20% boost to the base curve permanently for this spawn
        for rpm, torque in pairs(eng.torqueCurve) do
            if type(rpm) == "number" then
                eng.torqueCurve[rpm] = torque * 1.2
            end
        end
        -- Re-calculate internal engine data so UI and logic stays synced
        eng.torqueData = eng:getTorqueData()
        eng.maxTorque = eng.torqueData.maxTorque
        eng.maxPower = eng.torqueData.maxPower
    end
end

Master Multipliers (Restrictors)

If you want to create a "Restrictor Plate" or a dynamic power multiplier (e.g., for a "Valet Mode"), use the outputTorqueState property. This is a master multiplier for the final calculated torque.

local function updateGFX(dt)
    local eng = powertrain.getDevice("mainEngine")
    if eng then
        -- 0.8 = -20% power across the board
        eng.outputTorqueState = 0.8
    end
end

C. Reacting to Events (Hooks)

Instead of "hijacking" a function by overriding it, listen for the events that the game already triggers. This ensures your mod works alongside other mods.

Essential Hooks:

  • onInit(): Setup your mod's variables.
  • onVehicleReset(): Reset your state when the player presses 'R' or 'I'.
  • onBeamBroke(id, energy): Detect when a specific part of the car breaks.
  • updateGFX(dt): Use for visual/UI logic (runs at frame rate).
  • updateFixedStep(dt): Use for physics/torque logic (runs at 2000Hz).

4. Advanced Modding Patterns

Overriding vs. Hooking

Rule: Hooks are almost always better than Overrides. An Override replaces a function entirely, which can break other mods. If you must override (e.g., to change how a core module calculates a specific value), always store and call the original function.

The Safe Override Pattern

The order in which you call the original function is critical:

  1. Original FIRST (Recommended): Call the original function at the beginning of your wrapper. This ensures that the core game logic finishes all its calculations first, allowing your mod to have the "final word" and preventing your changes from being overwritten by the original function.
  2. Original LAST: Call the original function at the end. Use this only if your logic is meant to prepare data for the original function to process.
-- How to safely override a function
local originalFunc = someModule.importantFunction

someModule.importantFunction = function(arg1, arg2)
    -- 1. Call the original function FIRST
    -- This lets the game do its standard work
    local result = originalFunc(arg1, arg2)
    
    -- 2. Apply your custom logic AFTER
    -- Now you can modify the 'result' or set variables
    -- knowing the game won't overwrite them this frame.
    print("Game logic finished, now applying mod logic...")
    
    return result
end

Adding a Turbo Boost Button

This example shows how to combine input detection with powertrain manipulation.

local boostActive = false

local function updateGFX(dt)
    -- Check if throttle is fully pressed and clutch is released
    boostActive = electrics.values.throttle > 0.9 and (electrics.values.clutch or 0) < 0.1
end

local function updateFixedStep(dt)
    if boostActive then
        local eng = powertrain.getDevice("mainEngine")
        if eng then
            -- Apply 1.5x power multiplier (50% boost)
            eng.outputTorqueState = 1.5
            -- Visually show it on the dash
            electrics.values.boost_active = 1
        end
    else
        local eng = powertrain.getDevice("mainEngine")
        if eng then eng.outputTorqueState = 1.0 end
        electrics.values.boost_active = 0
    end
end

5. UI Visibility (Architectural Overrides)

If you want your modifications (like a 20% power boost) to show up in the Torque Curve UI App, you must safely override the engine's data reporting function.

A. Avoiding "Ghost Values"

A Ghost Value occurs when your mod changes the physics of the vehicle (e.g., adding torque via outputTorqueState) but fails to update the UI data. This leads to a confusing user experience where the vehicle feels stronger or weaker than the gauges and graphs indicate.

  • Rule: If you change how much power a wheel gets or how hot an engine gets, you MUST ensure that electrics.values and reporting functions like getTorqueData() reflect those changes.

B. The getTorqueData Override

The Torque Curve app calls eng:getTorqueData(). You can wrap this function to modify the results before the UI sees them.

local function onInit()
    local eng = powertrain.getDevice("mainEngine")
    if not eng then return end

    -- 1. Store the original function
    local orgGetTorqueData = eng.getTorqueData

    -- 2. Define the wrapper
    eng.getTorqueData = function(device)
        -- 3. Call the original to get the base curves
        local data = orgGetTorqueData(device)
        
        -- 4. Apply your multiplier to every point in the curves
        local multiplier = 1.2 -- 20% boost
        for _, curve in ipairs(data.curves) do
            for rpm, torque in pairs(curve.torque) do
                curve.torque[rpm] = torque * multiplier
            end
            for rpm, power in pairs(curve.power) do
                curve.power[rpm] = power * multiplier
            end
        end
        
        -- 5. Update the max values so the graph scales correctly
        data.maxTorque = data.maxTorque * multiplier
        data.maxPower = data.maxPower * multiplier
        
        return data
    end
end

6. Physical Consequences of Adding Power

When you add power via curve modification or outputTorqueState, the rest of the vehicle physics will react naturally to that force.

  • Torque Converter: It will handle the extra torque, but it will slip more. Since a torque converter uses fluid coupling, more torque results in more internal shear, which generates extra heat. If you add 200% more power, you may need to upgrade the torque converter's thermal capacity in JBeam or it will overheat and fail.
  • Drivetrain Stress: Every component downstream (Gearbox, Shafts, Differentials) has a physical limit defined in JBeam (e.g., beamStrength). Adding too much torque can cause these parts to snap or lock up if they aren't rated for the new load.
  • Thermals: The engine itself will generate more heat. If you only add power via Lua and don't touch the cooling system, the engine will overheat faster under load.

7. How to properly do common tasks

A. Adding a new Mode (e.g., Drift Mode)

Don't modify the existing driveModes.lua. Instead, create an extension that monitors a toggle and modifies steering or torque logic.

  1. Toggle: Use a key bind or UI button to set a flag in your extension.
  2. Logic: In updateFixedStep, if the flag is active, modify steering coefficients in electrics.values.steeringOverride or add torque to the wheels.

B. Creating a Telemetry Logger

  1. Capture: In updateGFX, collect values from electrics.values or obj:getPosition().
  2. Storage: Use a local table to buffer data.
  3. Output: Use dumpToFile or print to export data periodically.

8. Avoiding Interferences

  1. Namespace Your Electrics: If you create a custom signal, use a unique name (e.g., electrics.values.myMod_active = 1).
  2. Use auto Loading: Placing extensions in extensions/auto/ ensures they load without needing to modify the vehicle's main.lua.
  3. Check for Existence: Always check if a device exists before calling methods on it:
    local diff = powertrain.getDevice("rearDiff")
    if diff then diff:lockUp() end

See Also

  • Controller Template: Reference for controller structure.
  • Developer Recipes: Snippets for common tasks.
  • Cheat Sheet: One-liners for quick modding.
  • Powertrain Base Device: Methods available for torque manipulation.

62 Vehicle Scripting Recipes

Copy-paste solutions for powertrain control, JBeam manipulation, input handling, UI communication, and niche modding tasks.

Understanding Vehicle Damage Systems

How BeamNG's three damage systems work together — beamstate for physics, damageTracker for gameplay, and partCondition for persistence.

On this page

1. Extension vs. Controller2. Creating a Basic Extension3. Modifying Existing Systems (The Safe Way)A. The Data Bus (electrics.values)B. The Powertrain APIAdding Power (Architectural Solution)Master Multipliers (Restrictors)C. Reacting to Events (Hooks)4. Advanced Modding PatternsOverriding vs. HookingThe Safe Override PatternAdding a Turbo Boost Button5. UI Visibility (Architectural Overrides)A. Avoiding "Ghost Values"B. The getTorqueData Override6. Physical Consequences of Adding Power7. How to properly do common tasksA. Adding a new Mode (e.g., Drift Mode)B. Creating a Telemetry Logger8. Avoiding InterferencesSee Also