Emergency server help: get in touch

Luau Cheat Sheet: Type Annotations, Syntax and Roblox Basics

A compact Luau reference covering type annotations, the syntax Luau adds to Lua 5.1, its standard library extras and the Roblox basics you meet first.

Published 7 min read

Short answer: Luau is the gradually typed language derived from Lua 5.1 that Roblox uses. It adds optional type annotations (local port: number = 8080), --!strict checking, generics, string interpolation with backticks, continue, compound assignment (+=), if expressions, iteration without pairs, and library extras such as table.find and string.split. It drops goto, the io library and 64-bit integers. The tables below cover the syntax and the first Roblox APIs you will meet.

The language examples in this Luau cheat sheet were run on the Luau 0.740 VM on 9 October 2026. That run checks syntax and runtime behaviour, not type errors; type-checker rules and the Roblox section were checked against the official documentation.

Luau vs Lua 5.1 at a glance

Luau Cheat Sheet – short answer
In short: A compact Luau reference covering type annotations, the syntax Luau adds to Lua 5.1, its standard library extras and the Roblox basics you meet first.
Luau Cheat Sheet – what this guide covers
Luau Cheat Sheet: what this guide covers.
FeatureLua 5.1Luau
TypesDynamic onlyOptional annotations, inference, --!strict
String interpolationstring.format`Host {name}:{port}`
Skip an iterationNo continue (5.2+ uses goto)continue; goto is not supported
Compound assignmentn = n + 1n += 1, ..=, //= and others
Conditional expressiona and b or cif a then b else c
Iterate a tablepairs(t) / ipairs(t)for k, v in t do (pairs and ipairs still work)
Floor divisionmath.floor(a / b)a // b
NumbersDoubleDouble only (exact integers up to 2^53); no 5.3 integer subtype
BitwiseNonebit32 library; no & or | operators
Files and processesio, os.executeRemoved by sandboxing; os keeps time, clock, date, difftime
Finalisers__gcNot supported
ConstantsNoneconst LIMIT = 10 in recent Luau releases

Luau keeps getfenv and setfenv for compatibility, but using them disables some optimisations. Roblox also disables loadstring unless ServerScriptService.LoadStringEnabled is turned on, and then only on the server.

Variables, operators and string interpolation

SyntaxWhat it doesExample
local x: number = 1Local with an optional typelocal name: string = "web01"
const X = 10Binding that cannot be reassigned (the value can still change if it is a table)const MAX_PLAYERS = 12
`… {expr} …`Interpolated string; \{ for a literal braceprint(`sum {1 + 2}`)
+=, -=, *=, /=, //=, %=, ^=, ..=Compound assignment (statements, not expressions)score += 10
if c then a elseif d then b else eIf expression; else is requiredlocal label = if port == 443 then "https" else "http"
1_000_000, 0b1010, 0xFFDigit separators, binary and hex literalslocal mask = 0b_0101_0101
typeof(x)Like type, but names engine types ("Instance", "Vector3" on Roblox)typeof(part) == "Instance"
.., #, ~=, and/or/notAs in Lua 5.1local v = x or "default"

Backtick strings must be wrapped in parentheses when passed to a function: print(`hi`) works, print`hi` does not. A double brace {{ inside a backtick string is a parse error.

Type annotations

Type syntaxMeaningExample
number, string, boolean, nil, any, unknown, neverBuilt-in typeslocal ok: boolean = true
T?Optional: same as T | nillocal nick: string? = nil
A | B, A & BUnion and intersectionlocal id: string | number = 5
{number}Array of numberslocal scores: {number} = {1, 2, 3}
{[string]: number}Dictionarylocal counts: {[string]: number} = {}
{x: number, y: number}Table with named fieldstype Point = {x: number, y: number}
{read x: number}Read-only propertyWrites are type errors
"up" | "down"String literal typestype Dir = "up" | "down"
(number, string) -> booleanFunction typetype Callback = (number) -> ()
(): (number, string)Multiple return valueslocal function pair(): (number, string)
...: numberTyped varargslocal function sum(...: number): number
expr :: TType castlocal s = value :: string
typeof(expr) in a typeType of an existing value (not evaluated at runtime)type Config = typeof(DEFAULTS)
export typeMake a type visible to modules that require this oneexport type Server = {host: string}
--!strict
type Pair<T> = {first: T, second: T}

local function add(a: number, b: number): number
  return a + b
end

local function first<T>(list: {T}): T?
  return list[1]
end

local p: Pair<number> = {first = 1, second = 2}
print(add(1, 2), first({"a", "b"}), p.first)   --> 3  a  1
Mode (first line)Behaviour
--!nocheckNo type checking for the file
--!nonstrictDefault. Unknown types become any, so fewer errors are reported
--!strictFull inference; catches mismatches such as adding a string to a number

Narrow a union with a check before use: inside if typeof(v) == "string" then … end the checker treats v as a string, and after assert(x) an optional x is no longer nil.

Tables and the standard library extras

FunctionWhat it doesExample
table.find(t, v)Index of a value in an array, or niltable.find({"a", "b"}, "b") gives 2
table.create(n, v)Pre-sized array filled with vtable.create(3, 0)
table.clear(t)Empty a table but keep its capacitytable.clear(cache)
table.clone(t)Shallow copylocal copy = table.clone(defaults)
table.freeze(t) / table.isfrozen(t)Make read-only (shallow); writes raise an errorlocal CONFIG = table.freeze({…})
string.split(s, sep)Split into an arraystring.split("a,b,c", ",")
math.clamp(x, min, max)Limit to a rangemath.clamp(15, 0, 10) gives 10
math.round(x), math.sign(x)Round half away from zero; signmath.round(2.5) gives 3
bit32.band, bxor, lshiftBitwise operations on 32-bit valuesbit32.band(12, 10) gives 8
buffer.create(n)Fixed-size byte buffer with typed reads and writesbuffer.writeu32(b, 0, 42)
utf8.len(s)Character countutf8.len("héllo") gives 5

Control flow and generalised iteration

local cfg = {host = "db01", port = 3306}
for key, value in cfg do            -- no pairs() needed
  print(key, value)
end

for i = 1, 5 do
  if i % 2 == 0 then continue end   -- continue must be the last statement in its block
  print(i)                          --> 1, 3, 5
end

Plain tables iterate 1..#t in order first, then the remaining keys in no set order. Give a table or class an __iter metamethod to control what for … in obj do returns. while, repeat … until, numeric for and break work as in Lua 5.1.

Functions, errors and coroutines

SyntaxWhat it doesExample
local function f(a: number): stringTyped local functionlocal function greet(n: string): string
function<T>(x: T): TGeneric functionlocal function first<T>(list: {T}): T?
error(msg, 0)Raise without the location prefixerror("bad port", 0)
pcall(f, ...)Returns true, results or false, errlocal ok, err = pcall(fetch)
xpcall(f, handler, ...)With a message handler; extra arguments go to fxpcall(f, debug.traceback)
coroutine.wrap(fn)Generator functioncoroutine.yield(i) inside
coroutine.close(co)Close a suspended coroutineFrom Lua 5.4, supported in Luau
@native, @deprecatedFunction attributes@native local function hot() … end

Luau does not perform tail calls, so deep recursion that relies on them in standard Lua will hit the stack limit (about 20,000 Lua calls per thread).

Modules and typed classes

local Account = {}
Account.__index = Account

type AccountData = {owner: string, balance: number}
export type Account = typeof(setmetatable({} :: AccountData, Account))

function Account.new(owner: string): Account
  return setmetatable({owner = owner, balance = 0}, Account)
end

function Account.deposit(self: Account, amount: number)
  self.balance += amount
end

function Account:describe(): string
  return `{self.owner}: {self.balance}`
end

return Account   -- in a ModuleScript; callers use require(path.to.Account)

The typeof(setmetatable({} :: Data, Class)) pattern gives the checker both the fields and the methods. Annotating self explicitly, as in deposit, helps in --!strict files. Another module refers to the exported type as Account.Account after local Account = require(…).

Roblox basics

APIWhat it doesExample
Script / LocalScript / ModuleScriptRuns on the server / runs on the client / returns a value to requireServer logic in ServerScriptService
game:GetService(name)Get a servicelocal Players = game:GetService("Players")
Instance.new(class)Create an objectlocal part = Instance.new("Part")
obj.Parent = …Put it in the data modelpart.Parent = workspace
:WaitForChild(name) / :FindFirstChild(name)Wait for / look up a child (nil if missing)script.Parent:WaitForChild("Handle")
event:Connect(fn)Listen to an event; returns a connection :Disconnect()part.Touched:Connect(onTouch)
task.wait(s)Yield; returns the time actually waitedUse instead of the deprecated wait()
task.spawn(f), task.delay(s, f), task.defer(f), task.cancel(t)Schedule threadstask.delay(5, cleanup)
RemoteEvent:FireServer(…) / .OnServerEventClient-to-server messages; the server receives the player firstremote.OnServerEvent:Connect(function(player, data) … end)
local Players = game:GetService("Players")

Players.PlayerAdded:Connect(function(player: Player)
  print(`{player.Name} joined`)
end)

Treat everything a client sends through a RemoteEvent as untrusted input: check types and ranges on the server before acting on it. Wrap DataStore calls such as GetAsync in pcall, because they can fail on network or quota errors.

Official documentation (checked 9 October 2026): Luau syntax by example · An introduction to Luau types · Luau compatibility with Lua · Luau standard library · Roblox Creator Hub: Luau · Roblox Creator Hub: task library

Related guides: Lua Cheat Sheet: Lua 5.4 Syntax, Tables and Patterns · TypeScript Cheat Sheet: Types, Generics and tsconfig.

Reading a lot of our guides? Go ad-free with srvScripts Pro for $6 a month. It also pays for new guides. Cancel any time.

Share this guide

Free website test

Is your website set up right?

Check SSL, security headers, redirects, robots.txt, sitemap, llms.txt and security.txt in one test. It takes about 30 seconds.