Short answer: Lua 5.4 is a small language built around one data structure, the table, which serves as array, dictionary and object. Use local for every variable, .. to join strings, #t for length, ipairs and pairs to loop, pcall to catch errors, and metatables with __index for classes. Arrays start at 1, only nil and false are false, and 5.4 adds integers, //, <const> and <close> variables.
The examples in this Lua cheat sheet were run with Lua 5.4.6 on 9 October 2026. Lua 5.5 has since been released; this sheet targets 5.4.
Table of Contents
Values, types and operators
Syntax
What it does
Example
local x = 1
Local variable. Without local it is global
local port = 8080
nil, boolean, number, string, table, function
Main types; type(x) returns the name
type({}) gives "table"
math.type(x)
"integer" or "float" (nil for non-numbers)
math.type(1.0) gives "float"
/ and //
Float division / floor division
7 / 2 is 3.5, 7 // 2 is 3, -7 // 2 is -4
%, ^
Modulo; power (always a float)
2 ^ 10 gives 1024.0
&, |, ~, <<, >>, ~x
Bitwise on integers
3 ~ 5 (XOR) gives 6
==, ~=
Equal / not equal. No type coercion
"10" == 10 is false
and, or, not
Return an operand, not a bool
local v = x or "default"
tonumber(s, base), tostring(v)
Conversions
tonumber("0x10") is 16; tonumber("abc") is nil
math.tointeger(3.0)
Float to integer if exact
Returns fail (nil) otherwise
Only nil and false are false: 0 and "" are true. Integers are 64-bit and wrap around: math.maxinteger + 1 == math.mininteger is true.
Strings and patterns
Function
What it does
Example
..
Concatenation
"Host " .. name .. ":" .. port
#s vs utf8.len(s)
Bytes vs UTF-8 characters
#"héllo" is 6, utf8.len gives 5
s:upper(), s:sub(i, j)
Method syntax on strings; negative indexes count from the end
("abcdef"):sub(-3) gives "def"
string.format()
printf-style
string.format("%-8s %5d", h, p)
s:find(text, 1, true)
Plain find (4th argument disables patterns)
ip:find(".", 1, true)
s:match(pat)
First match; returns captures
line:match("^(%w+)%s*=%s*(%w+)$")
s:gmatch(pat)
Iterate over matches
for w in s:gmatch("%a+") do … end
s:gsub(pat, repl)
Replace all; also returns the count
("a,b"):gsub(",", ";")
string.rep(s, n, sep)
Repeat
string.rep("ab", 3, ",")
[[ … ]]
Long string, no escapes
Multi-line text
Lua patterns are not regular expressions. There is no | alternation and no {n} counts, and the escape character is %, not backslash.
Pattern item
Matches
%d, %a, %w, %s
Digit, letter, alphanumeric, whitespace (upper case negates: %S)
. and %.
Any character / a literal dot
*, +, -, ?
0 or more (greedy), 1 or more, 0 or more (lazy), optional
[^/]+
Character class and negation
^, $, ( )
Anchors and captures
(" x "):match("^%s*(.-)%s*$")
The usual trim idiom
Tables: arrays and dictionaries
Syntax
What it does
Example
{10, 20, 30}
Array part, indexes start at 1
list[1] is 10
{host = "db01", ["max-conn"] = 100}
Dictionary; brackets for keys that are not names
cfg.host, cfg["max-conn"]
#t
Length of a sequence (no nil holes)
list[#list + 1] = 40 appends
table.insert(t, v) / table.insert(t, 1, v)
Append / insert at a position
table.insert(list, 1, 5)
table.remove(t)
Remove and return the last (or given) element
table.remove(list, 1)
table.concat(t, sep)
Join strings and numbers
table.concat(list, ",")
table.sort(t, cmp)
Sort in place
table.sort(t, function(a, b) return a > b end)
table.unpack(t) / table.pack(...)
Spread / collect values (.n counts nils)
table.pack(1, nil, 3).n is 3
t.key = nil
Delete a key
cfg.port = nil
next(t) == nil
Is the table empty?
Works for any table
for i, v in ipairs(list) do print(i, v) end -- 1..n in order, stops at the first nil
for k, v in pairs(cfg) do print(k, v) end -- every key, order not defined
Control flow
Syntax
Example
if … then … elseif … else … end
if code >= 500 then … elseif code >= 400 then … end
Numeric for
for i = 10, 1, -3 do … end (start, stop, step)
Generic for
for line in io.lines(path) do … end
while / repeat … until
repeat i = i - 1 until i <= 0
break
Leaves the innermost loop
goto continue … ::continue::
Lua has no continue; this is the usual substitute
a and b or c
Ternary idiom; wrong if b can be nil or false
Functions, varargs and closures
Syntax
What it does
Example
local function f(a, b) … end
Local function (can call itself recursively)
local function greet(n) … end
greeting = greeting or "Hello"
Default arguments idiom
Missing arguments are nil
return a, b
Multiple return values
local ok, err = f()
... and select("#", ...)
Varargs; count including nils
local args = {...}
select(n, ...)
Values from position n
select(2, "a", "b", "c") gives b, c
Closures
Inner functions keep their upvalues
see below
obj:method(x)
Same as obj.method(obj, x)
srv:dsn()
local function counter()
local c = 0
return function() c = c + 1; return c end
end
local nextc = counter()
nextc()
print(nextc()) --> 2
Errors: pcall, error and assert
Call
What it does
Example
error(msg, level)
Raise; level 0 omits the file:line prefix
error("bad port", 0)
pcall(f, ...)
Protected call: returns true, results or false, err
local ok, err = pcall(load_cfg)
xpcall(f, handler)
As pcall, with a message handler
xpcall(f, debug.traceback)
assert(v, msg)
Raise if v is false or nil; returns all its arguments
local fh = assert(io.open(path))
return nil, "msg"
Library convention for soft failure
io.open returns nil and the reason
error({code = 42})
Any value can be an error object
Check with type(err) == "table"
Metatables and objects
local Server = {}
Server.__index = Server
function Server.new(host, port)
local self = setmetatable({}, Server)
self.host, self.port = host, port or 22
return self
end
function Server:dsn() return self.host .. ":" .. self.port end
Server.__tostring = function(s) return "Server(" .. s:dsn() .. ")" end
local srv = Server.new("db01", 3306)
print(srv:dsn(), tostring(srv)) --> db01:3306 Server(db01:3306)
Metamethod
Called when
__index
A missing key is read (table or function); basis of classes and defaults
__newindex
A missing key is assigned; use for read-only tables
__tostring
tostring(obj) or print(obj)
__eq, __lt, __le
Comparisons
__add, __concat, __len
Arithmetic, .. and #
__call
The table is called like a function
__close
A <close> variable goes out of scope (5.4)
__gc
The object is garbage-collected
For inheritance, give the subclass table a metatable whose __index is the parent: local Web = setmetatable({}, {__index = Server}), then Web.__index = Web.
New in Lua 5.4 and coroutines
Feature
Example
Note
Constant variable
local limit <const> = 10
Assigning again is a compile error
To-be-closed variable
local fh <close> = assert(io.open(p))
fh is closed when the block ends, even on error
Integer subtype (5.3+)
math.type(3) is "integer"
64-bit integers alongside floats
Warnings
warn("@on"), lua -W
Off by default
Coroutine generator
coroutine.wrap(function() coroutine.yield(1) end)
Call the result to resume
Coroutine control
coroutine.resume(co, x), coroutine.status(co)
Status is suspended, running or dead
Files, os and running commands
Task
Code
Read a whole file
local fh = assert(io.open(p, "r")); local s = fh:read("a"); fh:close()
Loop over lines
for line in io.lines(p) do … end
Write or append
io.open(p, "w") / io.open(p, "a"), then fh:write(s)
Command output
local p = io.popen("uptime"); local out = p:read("a"); p:close()
local json = require("cjson") (searches package.path and package.cpath)
For server admins: Lua is embedded in tools you may already run, but not always as 5.4. Redis scripts (EVAL) use Lua 5.1, and OpenResty uses LuaJIT, which is 5.1-compatible, so //, bitwise operators, goto (in Redis) and <close> are not available there. A quick status-code count over an access log in plain Lua 5.4:
local counts = {}
for line in io.lines("access.log") do
local status = line:match('" (%d%d%d) ')
if status then counts[status] = (counts[status] or 0) + 1 end
end
for code, n in pairs(counts) do print(code, n) end
In short: A compact Lua 5.4 reference with tables for operators, string patterns, tables, functions, error handling, metatables and file access.Lua Cheat Sheet: what this guide covers.
Necessary cookies enable essential site features like secure log-ins and consent preference adjustments. They do not store personal data.
None
Functional cookies support features like content sharing on social media, collecting feedback, and enabling third-party tools.
None
Analytical cookies track visitor interactions, providing insights on metrics like visitor count, bounce rate, and traffic sources.
None
Advertisement cookies deliver personalized ads based on your previous visits and analyze the effectiveness of ad campaigns.
None
Unclassified cookies are cookies that we are in the process of classifying, together with the providers of individual cookies.
None
Try:
Free checklist
cPanel and DirectAdmin server hardening checklist
A two-page PDF of the checks we run on every hosting server: SSH, firewall, PHP, mail, backups and updates. Get it with the srvScripts newsletter (about twice a month, one-click unsubscribe).
We send a confirmation link first; the download appears after you confirm. Privacy policy.
srvScripts GenieFinds the right tool, guide or fix
Tell me what is wrong, for example emails going to spam, SSL expired on example.com or 550 5.7.1 rejected. Add a domain or IP address and I run a quick check right here, for example check DNS for example.com.
Never paste passwords or keys. Unanswered questions are kept for up to 90 days to improve our guides, with keys, passwords, IP and e-mail addresses masked (best effort: leave out confidential details), and without your IP address, account or cookies. Details