Introduction to Metatables
Set and get metatables with setmetatable/getmetatable.
Introduction to Metatables is a free Lua Academy lesson on CoddyKit — lesson 1 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Lua Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
What is a Metatable?
A metatable is a regular Lua table that controls the behavior of another table. When Lua encounters an operation it can't perform on a table (like arithmetic or indexing a missing key), it looks for a corresponding metamethod in the metatable. This is Lua's extension mechanism for operator overloading and OOP.
local t = {}
local mt = {__index = function(_, k) return "default_" .. k end}
setmetatable(t, mt)
print(t.foo) -- default_foo
print(t.bar) -- default_bar
print(t[1]) -- default_1setmetatable and getmetatable
setmetatable(t, mt) sets the metatable of t to mt and returns t. getmetatable(t) returns the metatable, or nil if none. You can protect a metatable from being changed by setting mt.__metatable = "protected".
local t = {x = 10}
local mt = {}
setmetatable(t, mt)
print(getmetatable(t) == mt) -- true
-- Protect metatable
mt.__metatable = "locked"
print(getmetatable(t)) -- locked
-- setmetatable(t, {}) -- ERROR: cannot change protected metatable__index as Table
When __index is a table, Lua looks up the missing key in that table. This is the foundation of Lua's prototype inheritance. If the key is not in __index either, Lua checks its metatable, and so on up the chain.
local Animal = {sound = "...", legs = 4}
local Dog = setmetatable({sound = "Woof"}, {__index = Animal})
local Cat = setmetatable({sound = "Meow"}, {__index = Animal})
print(Dog.sound) -- Woof (own field)
print(Dog.legs) -- 4 (from Animal)
print(Cat.sound) -- Meow
print(Cat.legs) -- 4__index as Function
When __index is a function, Lua calls it with the table and the missing key. The function can compute or fetch the value dynamically. Return nil from __index to indicate "key not found."
local dynamic = setmetatable({}, {
__index = function(t, k)
if k:sub(1,4) == "get_" then
local field = k:sub(5)
return function(self) return self[field] end
end
return nil
end
})
dynamic.name = "Lua"
local getter = dynamic.get_name
print(getter(dynamic)) -- Luarawget and rawset
rawget(t, k) reads a table key without triggering __index. rawset(t, k, v) sets a key without triggering __newindex. Use these to bypass metamethods when you need direct table access inside a metamethod to avoid infinite recursion.
local logged = setmetatable({}, {
__newindex = function(t, k, v)
print("Setting", k, "=", v)
rawset(t, k, v) -- bypass __newindex
end
})
logged.x = 10 -- Setting x = 10
logged.x = 20 -- no log! key now exists in t
print(logged.x) -- 20Multiple Tables Sharing a Metatable
A single metatable can be shared by many tables. This is how Lua class systems work: all instances of a class share the same metatable, which holds the methods. Changes to the metatable affect all instances.
local Mt = {
__index = {
greet = function(self)
print("Hello from " .. (self.name or "?"))
end
}
}
local a = setmetatable({name="Alice"}, Mt)
local b = setmetatable({name="Bob"}, Mt)
a:greet() -- Hello from Alice
b:greet() -- Hello from Bob__metatable Field
Setting mt.__metatable to any value causes getmetatable to return that value instead of the real metatable. This prevents users from inspecting or replacing the metatable — useful for encapsulation and security.
local secret = setmetatable({}, {
__metatable = false, -- hide metatable
__index = {value = 42}
})
print(secret.value) -- 42
print(getmetatable(secret)) -- false (not the real mt)
-- setmetatable(secret, {}) -- ERRORChecking for Metatable
Use getmetatable(t) to check if an object has a specific metatable (for type checking). Since __metatable can mask the real metatable, use rawequal or a marker field for reliable isinstance checks.
local MyClass = {}
MyClass.__index = MyClass
MyClass.__metatable = MyClass -- use self as marker
function MyClass.new(x)
return setmetatable({x=x}, MyClass)
end
local obj = MyClass.new(5)
print(getmetatable(obj) == MyClass) -- true
print(obj.x) -- 5Metatables on Non-Tables
Strings in Lua already have a metatable (the string module). This is why you can call s:upper() on any string. You cannot set metatables on strings, numbers, or booleans from pure Lua — only on tables and userdata.
local s = "hello"
local mt = getmetatable(s)
-- String metatable has __index = string library
print(mt.__index == string) -- true
-- So all string functions are methods:
print(s:upper()) -- HELLO
print(s:rep(3, "-")) -- hello-hello-helloDebugging with Metatables
You can add a __tostring metamethod to control how tostring() and print() display a table. This is invaluable for debugging complex objects.
local Point = {}
Point.__index = Point
Point.__tostring = function(p)
return string.format("Point(%g, %g)", p.x, p.y)
end
function Point.new(x, y)
return setmetatable({x=x,y=y}, Point)
end
local p = Point.new(3, 4)
print(tostring(p)) -- Point(3, 4)
print(p) -- Point(3, 4) (print calls tostring)Quick Check
What happens when you access a key not found in a table that has a metatable with __index set to another table?
Recap: Metatables
Summary:
setmetatable(t, mt)/getmetatable(t)__index: table → prototype lookup; function → dynamic dispatchrawget/rawsetbypass metamethods- Shared metatables enable class-like behavior
__metatableprotects the metatable from inspection__tostringcontrols print output
Frequently asked questions
Is the “Introduction to Metatables” lesson free?
Yes — the full text of “Introduction to Metatables” is free to read here on the web, and the Lua Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Lua Academy course, upgrade to CoddyKit PRO.
What will I learn in “Introduction to Metatables”?
Set and get metatables with setmetatable/getmetatable. You practise Lua Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start Lua Academy?
No prior experience is required. Lua Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 1 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “Introduction to Metatables” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this Lua Academy lesson?
Yes. Every Lua Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- Introduction to Metatables
- __index and __newindex
- Arithmetic Metamethods
- __tostring and __len