luau-types
When to Use
Use this skill when the task is primarily about Luau's type system:
- Choosing between
--!strict,--!nonstrict, and other file type-checking modes. - Adding or correcting type annotations on variables, functions, tables, and modules.
- Designing APIs that preserve inference instead of collapsing to
any. - Modeling data with generics, unions, intersections, optionals, and tagged unions.
- Typing object-like tables, metatable-backed modules, and exported module surfaces.
- Writing or reviewing type functions and other advanced type-level utilities.
- Using Roblox class, datatype, enum, or
IsAknowledge only to improve static typing.
Do not use this skill when the task is mainly about:
- General Luau syntax, control flow, metatables, or standard-library usage outside typing concerns.
- Runtime performance, profiling, hot-path tuning, or allocation strategy.
- Roblox networking, replication, data storage, cloud APIs, or gameplay architecture.
Decision Rules
- Use this skill if the core question is "what should this type be?" or "how should this code type-check over time?"
- Prefer
--!strictguidance for new or actively maintained code unless the task explicitly targets transitional or legacy code. - Prefer inference-preserving designs over annotation-heavy designs when the inferred shape stays precise and readable.
- Prefer explicit exported aliases at module boundaries when consumers should share a stable contract.
- Use generics when the relationship between inputs and outputs matters; do not replace that relationship with
any. - Use tagged unions plus refinements when a value can be one of several structured cases.
- If the task shifts into pure language syntax, hand off to
luau-core. - If the task shifts into optimization or runtime cost tradeoffs, hand off to
luau-performance. - If the task requires Roblox runtime architecture beyond type names and type refinement, use the appropriate
roblox/*skill instead. - If unsure, exclude anything that is not directly needed to improve typing correctness, maintainability, or analyzer behavior.
Instructions
- Start by identifying the file mode expectation:
- --!strict for strong inference and early error detection. - --!nonstrict for transitional code where unresolved values would otherwise become noisy. - --!nocheck only when the task explicitly requires disabled analysis.
- Preserve useful inference before adding annotations everywhere. Add annotations where they clarify intent, stabilize module contracts, constrain
self, or prevent unwanted widening toany. - Prefer concrete aliases for shared shapes:
- records for structured data, - indexers for dictionaries, - {T} for arrays, - exported aliases for module-facing contracts.
- Use optionals, unions, and intersections deliberately:
- T? for T | nil, - tagged unions for state machines or result-like values, - intersections to combine compatible table capabilities or function signatures.
- Treat casts with
::as a precision tool, not a bypass. Use them to narrow overly generic inference, not to hide unrelated-type errors. - Design generics around relationships:
- preserve element type through transforms, - carry key/value relationships through containers, - avoid defaulting to any when a type parameter can express intent.
- Model tables according to how Luau analyzes them:
- unsealed tables can accumulate fields locally, - annotated or returned tables become sealed, - width subtyping applies to sealed records.
- For object-like modules, separate instance data from class behavior, derive the instance type from
setmetatable, and annotateselfexplicitly when methods need the shared class type. - Keep module API surfaces type-safe:
- export named aliases for consumer-facing data, - keep implementation details internal, - choose signatures that infer caller types cleanly.
- Use Roblox type knowledge only for annotations and refinements, such as
Instance,Part,Enum.Material, datatypes, andIsA-driven narrowing.
Using References
- Open
references/type-system-overview.mdfor file modes, structural typing, annotations, casts, and module-boundary guidance. - Open
references/basic-types-and-table-typing.mdfor builtin types, special types likeanyandunknown, function signatures, table states, and indexers. - Open
references/generics.mdfor generic aliases, generic functions, defaults on aliases, and inference-preserving container patterns. - Open
references/unions-and-intersections.mdfor result shapes, tagged unions, discriminants, and safe intersection usage. - Open
references/refinements.mdfor truthy checks,type(...)guards, equality narrowing, compound conditions, andassert-based narrowing. - Open
references/object-oriented-typing.mdfor metatable-backed class typing,selfannotations, constructor return types, and exported instance aliases. - Open
references/type-functions.mdfor analysis-time type computation, available libraries, and when advanced type-level transforms are justified. - Open
references/roblox-types-in-luau.mdfor Roblox class, datatype, enum, constructor, service, andIsAtyping behavior. - Do not open other skill references unless the request clearly crosses skill boundaries.
Checklist
- The chosen type-checking mode matches the maintenance goal of the file.
- Public module contracts are explicit where reuse matters.
- Inference is preserved where it remains precise.
anyis avoided unless intentionally opting out.- Tables are typed according to their actual shape and sealing behavior.
- Unions, intersections, and optionals reflect real states instead of vague catch-all types.
- Generic parameters encode input/output relationships that callers rely on.
- Method
selftyping is explicit where Luau cannot safely infer the shared class type. - Roblox types are used only to improve typing, not to drift into unrelated Roblox architecture.
- No general syntax tutorial, performance advice, or networking/data/cloud guidance is included.
Common Mistakes
- Leaving a variable unannotated in
--!nonstrictand unintentionally turning it intoany. - Replacing a useful generic relationship with
anyor an overly broad union. - Sealing a table too early with an annotation, then expecting to add fields later.
- Expecting method definitions with
:to automatically share a preciseselftype across the whole class. - Using
::to force unrelated conversions instead of fixing the underlying type design. - Building unions without a discriminant, then making downstream refinement difficult.
- Using intersections between incompatible primitives such as
string & number. - Mixing runtime Roblox architecture guidance into a type-only task.
Examples
Export a stable module contract
--!strict
export type User = {
id: number,
name: string,
nickname: string?,
}
local M = {}
function M.makeUser(id: number, name: string): User
return {
id = id,
name = name,
nickname = nil,
}
end
return MPreserve relationships with a generic function
--!strict
local function first<T>(items: {T}): T?
return items[1]
end
local a = first({1, 2, 3}) -- number?
local b = first({"x", "y"}) -- string?Refine a tagged union
--!strict
type Loading = { kind: "loading" }
type Ready<T> = { kind: "ready", value: T }
type Failed = { kind: "failed", message: string }
type State<T> = Loading | Ready<T> | Failed
local function readValue(state: State<number>): number?
if state.kind == "ready" then
return state.value
end
return nil
endType an object-like module with explicit self
--!strict
local Counter = {}
Counter.__index = Counter
type CounterData = {
value: number,
}
export type Counter = typeof(setmetatable({} :: CounterData, Counter))
function Counter.new(initialValue: number): Counter
return setmetatable({
value = initialValue,
}, Counter)
end
function Counter.increment(self: Counter, amount: number): number
self.value += amount
return self.value
end
return Counter