error
error raises a runtime error, stopping normal execution and propagating up the call stack until a protected context catches it. It’s how you signal that something has gone wrong and let the caller decide how to handle it.
Signature
error(message [, level])
Arguments:
message— the error value, typically a string but can be any Lua typelevel— which call stack level to blame in the error location (default: 1)
Returns: Nothing — error never returns normally.
Basic Behavior
error stops execution of the current function and propagates the error upward:
function divide(a, b)
if b == 0 then
error("division by zero")
end
return a / b
end
divide(10, 0)
-- stdin:1: division by zero
-- stack traceback:
-- [C]: in function 'error'
-- stdin:1: in function 'divide'
-- ...
When called inside pcall, the error is caught and returned as the second value:
local ok, err = pcall(function()
error("something went wrong")
end)
print(ok) -- false
print(err) -- something went wrong
The Message Can Be Any Value
While strings are the convention, error accepts any Lua value:
error(42) -- raises the number 42
error({code = "E1"}) -- raises a table
-- pcall receives whatever you pass:
local ok, val = pcall(error, {code = "E1"})
print(val.code) -- E1
In practice, almost all error messages are strings. But raising tables as error objects is useful when you need structured error data — error codes, metadata, stack context — that the handler can inspect rather than parse from a string.
The Level Parameter
level controls which call site appears in the error’s location information. This is purely cosmetic, but it matters for debugging.
Level 1 (default): blames the line where error was called.
function foo(n)
if n < 0 then
error("n must not be negative") -- blames this line
end
end
Level 2: blames the caller of the current function — use this when you’re validating arguments and want the error to point at the caller:
function foo(n)
if type(n) ~= "number" then
error("number expected, got " .. type(n), 2)
end
end
foo("hello")
-- stdin:1: number expected, got string
-- (points to the call site, not to line 3 of foo)
Level 0: omits the location entirely — useful when the error message is self-explanatory and the line number would just add noise:
error("config file not found", 0)
Common Patterns
Argument Validation
The most common use of error is validating function arguments:
function set_name(obj, name)
if type(name) ~= "string" then
error("name must be a string", 2)
end
if #name == 0 then
error("name cannot be empty", 2)
end
obj.name = name
end
Guard Clauses
Use guard clauses to exit early when preconditions fail:
function process(data)
if not data then
error("data is required")
end
if not data.url then
error("data.url is required")
end
-- proceed with processing
end
Raising Structured Errors
For APIs that need more than a message string:
function validate_config(cfg)
local errs = {}
if not cfg.host then table.insert(errs, "host is required") end
if not cfg.port then table.insert(errs, "port is required") end
if #errs > 0 then
error({
code = "INVALID_CONFIG",
errors = errs
})
end
end
local ok, err = pcall(validate_config, {})
if not ok and type(err) == "table" then
print(err.code) -- INVALID_CONFIG
print(err.errors[1]) -- host is required
end
How error Interacts with pcall and xpcall
error does not return — it performs a longjmp equivalent that unwinds the stack. The nearest active pcall or xpcall catches it:
pcall(function()
error("oops")
end)
-- returns false, "oops"
xpcall(function()
error("oops")
end, debug.traceback)
-- returns false, "stack traceback:\n ..."
xpcall(function()
error("oops")
end, function(err)
return err .. " (caught by handler)"
end)
-- returns false, "oops (caught by handler)"
Without a protected context, error terminates the program:
error("fatal")
print("this never runs")
-- program terminates with error message
Common Pitfalls
Forgetting the Level When Validating
If you validate arguments and use the default level, the error points inside your function rather than at the caller:
function foo(n)
if type(n) ~= "number" then
error("need a number") -- default level=1
end
end
foo("x")
-- stdin:3: need a number (refers to the error() call inside foo)
-- Caller has no idea which argument was wrong
Fix it with level=2 so the error points to the call site.
Raising nil
If you pass nil to error, Lua replaces it with a default message:
pcall(error, nil)
-- err = "error object is not a string"
This happens because Lua expects error objects to be strings or tables. Passing nil is almost always a bug.
Using error for Control Flow
error is for exceptional states, not normal control flow. If a condition is expected, use a return value instead:
-- Wrong:
function find_item(list, key)
if not list[key] then
error("key not found") -- this happens regularly
end
return list[key]
end
-- Better:
function find_item(list, key)
if not list[key] then
return nil -- expected outcome, handle with if/else
end
return list[key]
end
See Also
- /reference/core-functions/ref-pcall/ — protected call that catches errors
- /reference/core-functions/ref-print/ — output values (often used during debugging before shipping)
- /tutorials/error-handling-basics/ — error handling patterns and best practices in Lua