OOP in Lua: classes via metatables
Lua does not have classes. It has tables and a one-line trick called setmetatable that lets you fake them well enough to ship. This lesson teaches the canonical class pattern, single-level inheritance, and, just as important, when to skip OOP entirely and just use a plain table.
Build it
Make the resource folder
Inside your server's resources folder, create this folder:
resources/qu_oop_metatables
Create the files
Create this exact file layout:
resources/qu_oop_metatables/
fxmanifest.lua
server.lua
Write fxmanifest.lua
Open fxmanifest.lua and paste this:
fx_version 'cerulean'
game 'gta5'
server_script 'server.lua'
There is no lua54 'yes' line here, and you do not need one. As of June 2025 Cfx.re removed Lua 5.3: per the official manifest reference, Lua 5.4 is now the only runtime across all server builds, so every Lua script runs on 5.4 automatically and the lua54 directive is a deprecated no-op you can leave out. The metatable features taught here are core Lua and work the same either way. (You will still see lua54 'yes' in plenty of existing resources and framework manifests - it now has no effect, and is harmless whether you leave it or remove it.) See the resource-manifest reference for the current definition.
Write the lesson code
Open server.lua and paste this. It defines a Vehicle class, a PoliceVehicle subclass that inherits from it, and one command that proves both work:
-- Base class
local Vehicle = {}
Vehicle.__index = Vehicle
function Vehicle.new(model, plate)
return setmetatable({ model = model, plate = plate }, Vehicle)
end
function Vehicle:label()
return self.model .. ' [' .. self.plate .. ']'
end
-- Subclass: PoliceVehicle inherits from Vehicle
local PoliceVehicle = {}
PoliceVehicle.__index = PoliceVehicle
setmetatable(PoliceVehicle, { __index = Vehicle })
function PoliceVehicle.new(model, plate, callsign)
local self = Vehicle.new(model, plate)
self.callsign = callsign
return setmetatable(self, PoliceVehicle)
end
function PoliceVehicle:label()
return 'UNIT ' .. self.callsign .. ' - ' .. self.model .. ' [' .. self.plate .. ']'
end
RegisterCommand('cartest', function()
local car = Vehicle.new('sultan', 'QU123')
print('[qu_oop_metatables] ' .. car:label())
local cruiser = PoliceVehicle.new('police', 'LSPD01', 'Adam-12')
print('[qu_oop_metatables] ' .. cruiser:label())
end, true)
Start and test it
Open server.cfg and add this line:
ensure qu_oop_metatables
Save, then run this in the txAdmin Live Console:
restart qu_oop_metatables
Run this test from the same console:
cartest
Keep reading the full lesson
Sign in to start, then unlock every step of this lesson and the full FiveM School with a membership.
- How it works
- If something went wrong
- What you can do now
- Try it yourself
The remainder of OOP via metatables is available to FiveM School members.