RLS Studios
ProjectsPatreonCommunityDocsAbout
Join Patreon
BeamNG Modding Docs

Guides

Lua Classes & MetatablesProven GE PatternsAnti-Patterns to AvoidError 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

Lua Modding FAQ

Quick answers to the most common BeamNG Lua modding questions — extension structure, data persistence, UI communication, timers, and more.

Quick answers to questions that come up constantly in BeamNG modding. Each answer includes a code example you can copy directly.


How do I structure an extension?

Return a plain table M with dot-notation functions. No classes, no metatables, no self, no colon syntax.

local M = {}

local function onExtensionLoaded()
  log('I', 'myMod', 'Extension loaded')
end

local function doThing(value)
  -- logic here
end

M.onExtensionLoaded = onExtensionLoaded
M.doThing = doThing

return M

Never write function M:doThing() or M.doThing = function() inline. Use local function then export at the bottom.


How do I persist data across level changes?

Register your extension in your mod script (scripts/mymod/modScript.lua) with setExtensionUnloadMode("mymod_feature", "manual") and call loadManualUnloadExtensions(). Local variables in your extension file persist as long as the extension stays loaded.

-- scripts/mymod/modScript.lua
setExtensionUnloadMode("mymod_feature", "manual")
loadManualUnloadExtensions()

Do NOT put setExtensionUnloadMode inside the extension itself.


How do I communicate between extensions?

Broadcast to all extensions via hooks:

extensions.hook('mymod_somethingHappened', data)

Call another extension directly:

if other_extension then
  other_extension.doThing()
end

How do I send data to the UI?

Use guihooks.trigger() to push data from Lua to the UI layer:

guihooks.trigger('MyModDataUpdate', { score = 100, level = 3 })

How do I call Lua from the UI?

Use bngApi.engineLua() in your JavaScript/Angular code:

bngApi.engineLua("extensions.mymod_feature.doThing()")

To pass data:

bngApi.engineLua('extensions.mymod_feature.setValue(' + bngApi.serializeToLua(value) + ')')

How do I save and load data?

Use jsonWriteFile() and jsonReadFile():

-- Save
local data = { highScore = 500, playerName = "Driver" }
jsonWriteFile("settings/mymod/save.json", data)

-- Load
local data = jsonReadFile("settings/mymod/save.json") or {}

How do I find vehicles?

Get the player vehicle:

local veh = be:getPlayerVehicle(0)

Get a vehicle by ID:

local veh = be:getObjectByID(id)

How do I log messages?

Use log() with a level, tag, and message:

log('I', 'myMod', 'Something happened')   -- Info
log('W', 'myMod', 'This looks wrong')     -- Warning
log('E', 'myMod', 'Something broke')      -- Error
log('D', 'myMod', 'Debug details here')   -- Debug

How do I use timers?

There is no setTimeout equivalent. Use the onUpdate hook with an accumulator:

local M = {}
local timer = 0

local function onUpdate(dtReal, dtSim, dtRaw)
  timer = timer + dtReal
  if timer >= 5.0 then
    timer = 0
    -- do something every 5 seconds
  end
end

M.onUpdate = onUpdate

return M

Should I use classes?

Usually no — but sometimes yes. Extensions themselves should always be plain M tables. But within an extension, classes are the right tool when you need:

  • Multiple instances of the same thing (e.g. managing several parcels, routes, or NPCs)
  • Encapsulated state that travels together (data + methods as one unit)
  • Shared behavior across instances via metatables

When NOT to use classes

For extensions, singletons, and one-off modules — just use the standard M table pattern:

local M = {}

local function functionName(args)
  -- implementation
end

M.functionName = functionName

return M

When classes make sense

When you're creating multiple instances that each hold their own state:

local M = {}

-- Class definition
local Parcel = {}
Parcel.__index = Parcel

function Parcel.new(origin, destination, weight)
  return setmetatable({
    origin = origin,
    destination = destination,
    weight = weight,
    delivered = false,
  }, Parcel)
end

function Parcel:markDelivered()
  self.delivered = true
end

function Parcel:getInfo()
  return string.format("%s → %s (%dkg)", self.origin, self.destination, self.weight)
end

-- Extension uses the class internally
local activeParcels = {}

local function createParcel(origin, dest, weight)
  local p = Parcel.new(origin, dest, weight)
  table.insert(activeParcels, p)
  return p
end

M.createParcel = createParcel

return M

The key distinction: the extension is still a plain M table — classes live inside it for managing instances. Don't make the extension itself a class.

👉 Full guide: Lua Classes — constructors, inheritance, patterns, and real BeamNG examples.


What is the difference between GE and VE?

  • GE (Game Engine) runs globally. It manages the world, UI, scenarios, and all vehicles. Extensions live in lua/ge/extensions/.
  • VE (Vehicle Engine) runs per-vehicle. It handles physics, powertrain, electrics, and vehicle-specific logic. Extensions live in lua/vehicle/extensions/.

They communicate across the boundary:

-- GE to VE
local veh = be:getPlayerVehicle(0)
veh:queueLuaCommand("myVehicleExtension.doThing()")

-- VE to GE
obj:queueGameEngineLua("extensions.mymod_feature.onVehicleData('" .. data .. "')")

How do I run code on a specific vehicle?

Use queueLuaCommand() on the vehicle object:

local veh = be:getPlayerVehicle(0)
veh:queueLuaCommand("myVehicleExtension.doThing()")

Or by vehicle ID:

local veh = be:getObjectByID(vehicleId)
veh:queueLuaCommand("electrics.values.headlights = 1")

See Also

  • Getting Started - First mod walkthrough
  • Creating Extensions - Full extension reference
  • Mod Scripts - Extension persistence
  • Hook Catalog - All available hooks
  • Common Patterns - Proven patterns
  • Anti-Patterns - Mistakes to avoid
  • Lua Classes - Metatables, constructors, and inheritance

Performance Optimization

How to keep your BeamNG Lua mod fast — avoiding GC pressure, caching lookups, efficient string handling, and proper timer patterns.

Creating HUD Apps

How to create UI apps (HUD widgets) for BeamNG.drive — directory structure, AngularJS directives, receiving Lua data, and styling.

On this page

How do I structure an extension?How do I persist data across level changes?How do I communicate between extensions?How do I send data to the UI?How do I call Lua from the UI?How do I save and load data?How do I find vehicles?How do I log messages?How do I use timers?Should I use classes?When NOT to use classesWhen classes make senseWhat is the difference between GE and VE?How do I run code on a specific vehicle?See Also