getmetatable
getmetatable(object) Syntax
getmetatable(object)
getmetatable returns the metatable of object, or the value of the metatable’s __metatable field if one is set. The single argument is required; calling with zero arguments raises a bad argument #1 error at the call site.
getmetatable is the read-side mirror of setmetatable. Together they form the basic stdlib interface for reflection and introspection of any Lua value, and they are the only sanctioned way to attach and read metatables from pure Lua.
Parameters
object (any, required): the value whose metatable you want to inspect. Accepts every Lua type: nil, boolean, number, string, function, userdata (full or light), thread, or table. The function never raises on type. It inspects the value’s metatable slot and applies the three-way return rule below.
Return Value
The function has three distinct outcomes, and callers have to know which one they got:
nilifobjecthas no metatable at all. This is the default for tables that have not hadsetmetatablecalled on them, and for every number, boolean, function, thread, andnil.- The actual metatable table, when
objectis a table or full userdata whose metatable has no__metatablefield. The returned table is the same reference that was set. Identity comparisons work. - The value of the metatable’s
__metatablefield, when one is set. This is the opaque guard value chosen by the metatable’s owner, commonly a string like"locked"or a sentinel table. It is not guaranteed to be a metatable, and you cannot read metamethods through it.
The three-way contract matters: getmetatable(t) ~= nil is not the same as “I can read the metamethods” once a library has installed a __metatable guard.
Examples
Roundtrip with setmetatable
local t = {}
local mt = {
__tostring = function(self) return "I am t" end,
}
setmetatable(t, mt)
local got = getmetatable(t)
print(got == mt) -- true
print(getmetatable(t) == mt) -- true (same value)
print(t) -- I am t (via __tostring)
The snippet builds a fresh table, attaches a metatable that defines a __tostring metamethod, then reads the metatable back with getmetatable and confirms the identity. Both reference comparisons return true because getmetatable returns the same table object that was attached, not a copy. The third line exercises the metamethod by asking the runtime to convert t to a string. Running it prints three lines: two booleans and the string the metamethod produced. Output:
true
true
I am t
The canonical use: attach a metatable, read it back, and the reference matches. tostring here resolves through the __tostring metamethod, which is why the final print call shows a string instead of a table address.
__metatable protection
local mt = { __add = function(a, b) return "added!" end }
mt.__metatable = "locked"
local t = setmetatable({}, mt)
print(getmetatable(t)) -- locked (not the real mt)
print(getmetatable(t).__add) -- nil (a string has no __add field)
local ok, err = pcall(setmetatable, t, {})
print(err) -- input:11: cannot change protected metatable
The example installs a metatable whose __metatable field holds the string "locked", attaches it to a table, then shows that getmetatable returns the guard value instead of the real metatable table. The pcall around setmetatable catches the error raised when the code tries to overwrite a protected metatable, which is the second half of the protection contract. Output:
locked
nil
input:11: cannot change protected metatable
The __metatable field does two things at once. It hides the real metatable from getmetatable, and it makes setmetatable raise an error. Library authors use this to keep callers from poking at internals. The contract is documented in the Lua 5.4 manual §6.1 and the metatables guide.
Strings have a built-in metatable
print(getmetatable("hello")) -- table: 0x...
print(getmetatable("hello").__index == string) -- true
print(("hello"):upper()) -- HELLO
The string library installs a shared metatable on every string value; that metatable’s __index points at the string library table. This is the whole reason ("hello"):upper() works. Method syntax on strings is a side effect of the metatable you can read with getmetatable. Trying to overwrite it with setmetatable("hi", mt) raises an error, because all strings share the same metatable.
Return is nil for un-set types
print(getmetatable({})) -- nil
print(getmetatable(42)) -- nil
print(getmetatable(true)) -- nil
print(getmetatable(io.read)) -- nil
Tables are the only type where you commonly see a non-nil result from getmetatable in pure Lua. Numbers, booleans, functions, threads, and nil itself all have no metatable by default. The function returns nil for each.
Behavior by Type
| Type | Default metatable | Notes |
|---|---|---|
table | none | Set with setmetatable; each table can have its own. |
Full userdata | none (from Lua) | C code attaches one. Same per-instance rules as tables. |
Light userdata | none | A raw C pointer; getmetatable returns nil. |
string | yes (shared) | Set by the string library; __index is the string table. Read-only from Lua. |
number, boolean, nil, function, thread | none | getmetatable always returns nil. |
Two tables can share the same metatable object, in which case getmetatable(a) == getmetatable(b) returns true. Equality is by reference, not by structure. The weak tables guide walks through the __mode field that often rides along on shared metatables.
Common Mistakes
- Assuming the return is always a table or
nil. The__metatablebranch returns whatever the owner put there: a string, a number, a sentinel table. Indexing into it without a guard check will silently returnnilfields. - Chaining
getmetatable(t).__indexacross a__metatableguard. If the metatable is protected, you get the guard value back, not the real__indextarget. Guard withpcallor check the type first. - Trying to
setmetatablea string. Strings already have a metatable, and it is shared across every string in the program. Lua raises an error. - Using
getmetatableas a public “is this table of class X?” check. It only works if neither side has installed a__metatablefield. Many libraries do, so the check is not reliable as an API contract. Pair it withtypewhen you need a portable test.
Probing a value’s metamethods safely
When you don’t know whether the owner has installed a __metatable guard, the naive getmetatable(t).__index lookup silently returns the wrong thing. A small helper keeps the probe honest:
local function probe_index(t)
local mt = getmetatable(t)
if type(mt) ~= "table" then
return nil, "guarded" -- __metatable set, or no metatable
end
local index = mt.__index
if type(index) == "table" then
return index, "table"
elseif type(index) == "function" then
return index, "function"
end
return mt, "raw"
end
local t = setmetatable({}, { __index = string })
print(probe_index(t)) -- table: 0x... table
local locked = setmetatable({}, { __metatable = "no" })
print(probe_index(locked)) -- nil guarded
The helper returns the underlying __index target when it’s reachable, plus a tag telling you how the chain is wired: a table, a function, or a raw metatable with no __index redirection. The type(mt) ~= "table" line is what makes it correct under a __metatable guard, because without it indexing into the guard value would either crash on a string or hand back the wrong object on a sentinel table.
Compatibility Notes
The three-way return contract has been stable from Lua 5.1 through 5.4. Code that handles all three branches runs on every modern interpreter. LuaJIT matches the same behaviour because it is 5.1-compatible. In Roblox’s Luau, getmetatable works identically and shows up frequently because Roblox instances are userdata with shared metatables. The Roblox scripting objects tutorial covers the patterns in practice.
If you need to look up metamethods without triggering __index chains, rawget and rawset are the raw-table access primitives that Lua itself uses internally for metatable dispatch, and grasping that distinction is what separates someone who dabbles with metatables from someone who can actually debug a third-party library whose internals refuse to leak through normal reflection.
See Also
setmetatable: the setter that pairs withgetmetatable.type: the natural first step before inspecting a value.tostring: reads the__tostringmetamethod.- Metatables and Metamethods in Lua: the conceptual guide.
- Weak Tables in Lua: uses metatables with the
__modefield. - Metatables: Introduction: beginner entry point.
- Metamethods Deep Dive: what every key in the returned table does.
- Classes with Metatables: practical OOP, where
getmetatableis the “what’s my class?” check. - Operator Overloading: covers
__add,__concat, and the other arithmetic keys.