Skip to main content
TRACK B·PRODUCTION ENGINEERING·Verified June 2026 · Lua 5.4 · ox_lib 3.x
Learning with an AI assistant?
Copies this lesson plus 2026 ground rules (no lua54 'yes', Cfx.re Portal, correct callback signatures) as a ready-to-paste mentor prompt.

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.

You'll learn
setmetatable, __index = self, constructor pattern, : vs . calls, inheritance via base class
Time
~22 minutes
Prereqs
Lessons 5 (tables) and 6 (functions)
Outcome
You can write a Vehicle class with a PoliceVehicle subclass and know when to throw it away
BEFORE YOU START

Build it

Make the resource folder

The server has one folder for this lesson.

Inside your server's resources folder, create this folder:

code
resources/qu_oop_metatables

Create the files

Every file named in the manifest exists.

Create this exact file layout:

code
resources/qu_oop_metatables/
fxmanifest.lua
server.lua

Write fxmanifest.lua

FiveM knows which files to load.

Open fxmanifest.lua and paste this:

code
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

The topic is now represented by runnable 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:

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

The expected proof appears in the server console.

Open server.cfg and add this line:

code
ensure qu_oop_metatables

Save, then run this in the txAdmin Live Console:

code
restart qu_oop_metatables

Run this test from the same console:

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

Still ahead in this lesson
  • 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.