luau-types
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:
--!strictfor strong inference and early error detection.--!nonstrictfor transitional code where unresolved values would otherwise become noisy.--!nocheckonly 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?forT | 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
anywhen 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 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
More from stackfox-labs/luau-skills
roblox-networking
Use for Roblox multiplayer communication across the client-server boundary: designing RemoteEvent, UnreliableRemoteEvent, and RemoteFunction flows; validating client requests; handling replication-aware gameplay; applying rate limits and anti-exploit checks; reasoning about network ownership, server-authority patterns, Input Action System use in authoritative gameplay, and streaming-sensitive multiplayer correctness.
35roblox-core
Use for foundational Roblox experience development: deciding what runs on the client or server, where scripts and modules belong, how to structure reusable code, and how to handle everyday services, attributes, bindables, workspace objects, input, camera, raycasts, collisions, and CFrame-based gameplay scripting in Studio.
29luau-performance
Use for Luau performance work focused on profiling hotspots, allocation-aware code structure, table and iteration costs, builtin and function-call fast paths, compiler/runtime optimization behavior, and environment constraints that change execution speed.
29roblox-api
Use for Roblox Engine API lookup during implementation: finding the correct engine class or service, confirming properties, methods, events, callbacks, datatypes, enums, globals, and built-in libraries, and verifying parameter, return, and property usage for a known engine task. Prefer this skill when the problem is referential rather than architectural.
27roblox-data
Use for Roblox persistent data and cross-server state design: choosing between DataStoreService, OrderedDataStore, MemoryStoreService, and MessagingService; designing save and load flows, schema shape, versioning, metadata, retries, quotas, observability, and concurrency-safe coordination across servers.
22roblox-oauth
Use for Roblox OAuth 2.0 work: registering an OAuth app, choosing confidential versus public client flows, implementing authorization code flow with PKCE, handling authorization callbacks and token refresh safely, selecting minimal scopes for Open Cloud access, and troubleshooting OAuth-specific auth failures.
3