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
IsA knowledge 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
--!strict guidance 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 to any.
- 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 annotate self explicitly 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, and IsA-driven narrowing.
Using References
- Open
references/type-system-overview.md for file modes, structural typing, annotations, casts, and module-boundary guidance.
- Open
references/basic-types-and-table-typing.md for builtin types, special types like any and unknown, function signatures, table states, and indexers.
- Open
references/generics.md for generic aliases, generic functions, defaults on aliases, and inference-preserving container patterns.
- Open
references/unions-and-intersections.md for result shapes, tagged unions, discriminants, and safe intersection usage.
- Open
references/refinements.md for truthy checks, type(...) guards, equality narrowing, compound conditions, and assert-based narrowing.
- Open
references/object-oriented-typing.md for metatable-backed class typing, self annotations, constructor return types, and exported instance aliases.
- Open
references/type-functions.md for analysis-time type computation, available libraries, and when advanced type-level transforms are justified.
- Open
references/roblox-types-in-luau.md for Roblox class, datatype, enum, constructor, service, and IsA typing 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.
any is 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
self typing 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
--!nonstrict and unintentionally turning it into any.
- Replacing a useful generic relationship with
any or 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 precise self type 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 M
Preserve 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
end
Type 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
1---2name: luau-types3description: Use for Luau type-system work focused on strictness modes, annotations, inference-aware API design, generics, refinements, advanced type patterns, and Roblox-aware type usage at the type level.4---56# luau-types78## When to Use910Use this skill when the task is primarily about Luau's type system:1112- Choosing between `--!strict`, `--!nonstrict`, and other file type-checking modes.13- Adding or correcting type annotations on variables, functions, tables, and modules.14- Designing APIs that preserve inference instead of collapsing to `any`.15- Modeling data with generics, unions, intersections, optionals, and tagged unions.16- Typing object-like tables, metatable-backed modules, and exported module surfaces.17- Writing or reviewing type functions and other advanced type-level utilities.18- Using Roblox class, datatype, enum, or `IsA` knowledge only to improve static typing.1920Do not use this skill when the task is mainly about:2122- General Luau syntax, control flow, metatables, or standard-library usage outside typing concerns.23- Runtime performance, profiling, hot-path tuning, or allocation strategy.24- Roblox networking, replication, data storage, cloud APIs, or gameplay architecture.2526## Decision Rules2728- Use this skill if the core question is "what should this type be?" or "how should this code type-check over time?"29- Prefer `--!strict` guidance for new or actively maintained code unless the task explicitly targets transitional or legacy code.30- Prefer inference-preserving designs over annotation-heavy designs when the inferred shape stays precise and readable.31- Prefer explicit exported aliases at module boundaries when consumers should share a stable contract.32- Use generics when the relationship between inputs and outputs matters; do not replace that relationship with `any`.33- Use tagged unions plus refinements when a value can be one of several structured cases.34- If the task shifts into pure language syntax, hand off to `luau-core`.35- If the task shifts into optimization or runtime cost tradeoffs, hand off to `luau-performance`.36- If the task requires Roblox runtime architecture beyond type names and type refinement, use the appropriate `roblox/*` skill instead.37- If unsure, exclude anything that is not directly needed to improve typing correctness, maintainability, or analyzer behavior.3839## Instructions40411. Start by identifying the file mode expectation:42 - `--!strict` for strong inference and early error detection.43 - `--!nonstrict` for transitional code where unresolved values would otherwise become noisy.44 - `--!nocheck` only when the task explicitly requires disabled analysis.452. Preserve useful inference before adding annotations everywhere. Add annotations where they clarify intent, stabilize module contracts, constrain `self`, or prevent unwanted widening to `any`.463. Prefer concrete aliases for shared shapes:47 - records for structured data,48 - indexers for dictionaries,49 - `{T}` for arrays,50 - exported aliases for module-facing contracts.514. Use optionals, unions, and intersections deliberately:52 - `T?` for `T | nil`,53 - tagged unions for state machines or result-like values,54 - intersections to combine compatible table capabilities or function signatures.555. Treat casts with `::` as a precision tool, not a bypass. Use them to narrow overly generic inference, not to hide unrelated-type errors.566. Design generics around relationships:57 - preserve element type through transforms,58 - carry key/value relationships through containers,59 - avoid defaulting to `any` when a type parameter can express intent.607. Model tables according to how Luau analyzes them:61 - unsealed tables can accumulate fields locally,62 - annotated or returned tables become sealed,63 - width subtyping applies to sealed records.648. For object-like modules, separate instance data from class behavior, derive the instance type from `setmetatable`, and annotate `self` explicitly when methods need the shared class type.659. Keep module API surfaces type-safe:66 - export named aliases for consumer-facing data,67 - keep implementation details internal,68 - choose signatures that infer caller types cleanly.6910. Use Roblox type knowledge only for annotations and refinements, such as `Instance`, `Part`, `Enum.Material`, datatypes, and `IsA`-driven narrowing.7071## Using References7273- Open `references/type-system-overview.md` for file modes, structural typing, annotations, casts, and module-boundary guidance.74- Open `references/basic-types-and-table-typing.md` for builtin types, special types like `any` and `unknown`, function signatures, table states, and indexers.75- Open `references/generics.md` for generic aliases, generic functions, defaults on aliases, and inference-preserving container patterns.76- Open `references/unions-and-intersections.md` for result shapes, tagged unions, discriminants, and safe intersection usage.77- Open `references/refinements.md` for truthy checks, `type(...)` guards, equality narrowing, compound conditions, and `assert`-based narrowing.78- Open `references/object-oriented-typing.md` for metatable-backed class typing, `self` annotations, constructor return types, and exported instance aliases.79- Open `references/type-functions.md` for analysis-time type computation, available libraries, and when advanced type-level transforms are justified.80- Open `references/roblox-types-in-luau.md` for Roblox class, datatype, enum, constructor, service, and `IsA` typing behavior.81- Do not open other skill references unless the request clearly crosses skill boundaries.8283## Checklist8485- The chosen type-checking mode matches the maintenance goal of the file.86- Public module contracts are explicit where reuse matters.87- Inference is preserved where it remains precise.88- `any` is avoided unless intentionally opting out.89- Tables are typed according to their actual shape and sealing behavior.90- Unions, intersections, and optionals reflect real states instead of vague catch-all types.91- Generic parameters encode input/output relationships that callers rely on.92- Method `self` typing is explicit where Luau cannot safely infer the shared class type.93- Roblox types are used only to improve typing, not to drift into unrelated Roblox architecture.94- No general syntax tutorial, performance advice, or networking/data/cloud guidance is included.9596## Common Mistakes9798- Leaving a variable unannotated in `--!nonstrict` and unintentionally turning it into `any`.99- Replacing a useful generic relationship with `any` or an overly broad union.100- Sealing a table too early with an annotation, then expecting to add fields later.101- Expecting method definitions with `:` to automatically share a precise `self` type across the whole class.102- Using `::` to force unrelated conversions instead of fixing the underlying type design.103- Building unions without a discriminant, then making downstream refinement difficult.104- Using intersections between incompatible primitives such as `string & number`.105- Mixing runtime Roblox architecture guidance into a type-only task.106107## Examples108109### Export a stable module contract110111```luau112--!strict113114export type User = {115 id: number,116 name: string,117 nickname: string?,118}119120local M = {}121122function M.makeUser(id: number, name: string): User123 return {124 id = id,125 name = name,126 nickname = nil,127 }128end129130return M131```132133### Preserve relationships with a generic function134135```luau136--!strict137138local function first<T>(items: {T}): T?139 return items[1]140end141142local a = first({1, 2, 3}) -- number?143local b = first({"x", "y"}) -- string?144```145146### Refine a tagged union147148```luau149--!strict150151type Loading = { kind: "loading" }152type Ready<T> = { kind: "ready", value: T }153type Failed = { kind: "failed", message: string }154type State<T> = Loading | Ready<T> | Failed155156local function readValue(state: State<number>): number?157 if state.kind == "ready" then158 return state.value159 end160161 return nil162end163```164165### Type an object-like module with explicit `self`166167```luau168--!strict169170local Counter = {}171Counter.__index = Counter172173type CounterData = {174 value: number,175}176177export type Counter = typeof(setmetatable({} :: CounterData, Counter))178179function Counter.new(initialValue: number): Counter180 return setmetatable({181 value = initialValue,182 }, Counter)183end184185function Counter.increment(self: Counter, amount: number): number186 self.value += amount187 return self.value188end189190return Counter191```