MCPInstall

roblox-luau

Modern, type-safe Luau for Roblox, covering --!strict and the new type solver, generics, string interpolation, if-expressions, task library, require-by-string, module/OOP patterns, error handling with retries, buffer/vector, and legacy APIs to avoid. Use when writing, refactoring, or explaining any Roblox script, fixing type errors, or modernizing old Lua code.

Installs
474
Last 24h
+24
Trending rank
#5058
Security
Not audited

Install

Works with every agent the skills CLI supports.

npx skills add https://github.com/el4cteo/roblox-skills --skill roblox-luau
  1. 01

    Install

    Run the command in your project. The CLI asks which agents to install for.

  2. 02

    Describe the task

    Ask your agent for something this skill covers. It loads roblox-luau automatically when relevant.

  3. 03

    Review the result

    The agent follows the SKILL.md steps, scripts and references bundled with the skill.

SKILL.md

The instructions your agent reads when the skill is triggered.

Modern Luau for Roblox

Luau is Roblox's gradually-typed Lua 5.1 derivative. Engine and language move fast; many patterns from older tutorials (and older training data) are deprecated. Apply these defaults to every script you write unless the project clearly uses another convention.

Defaults for all generated code

  1. --!strict at the top of new ModuleScripts (and Scripts when practical). Type public functions.
  2. Get services with game:GetService("Name") at the top of the file, never game.Name.
  3. Use the task library: task.wait, task.spawn, task.defer, task.delay, task.cancel. Never wait, spawn, delay (deprecated, throttled, imprecise).
  4. Yielding engine calls that can fail (DataStore, HTTP, Marketplace, Teleport, *Async) go inside pcall.
  5. Prefer the *Async variants of APIs; Roblox renamed most yielding methods (LoadCharacterAsync, GetProductInfoAsync, UserHasBadgeAsync, ...). See the table below.
  6. Disconnect connections / destroy instances you create (see "Cleanup" below).
  7. Naming: PascalCase for services, modules, classes, types, and instance names; camelCase for locals and functions; SCREAMING_SNAKE_CASE for constants.
  8. Tabs for indentation (StyLua default in the Roblox ecosystem), double quotes.

Modern syntax cheat sheet

local Players = game:GetService("Players")

local count = 0
count += 1 -- compound assignment: += -= *= /= //= %= ^= ..=

local label = if count > 1 then "many" else "one" -- if-expression (no `and/or` pitfalls)
print(`Player count: {#Players:GetPlayers()} ({label})`) -- string interpolation

for index, player in Players:GetPlayers() do -- generalized iteration: no pairs/ipairs needed
	if player.AccountAge < 1 then
		continue -- continue is a keyword
	end
	print(index, player.Name)
end

local half = 7 // 2 -- floor division → 3
local frozen = table.freeze({ Speed = 16 }) -- read-only table; writes throw
local list = table.create(100, 0) -- preallocated array
local copy = table.clone(frozen) -- shallow copy (unfrozen)
local mid = math.lerp(0, 10, 0.5) -- also: math.map, math.clamp, math.round, math.sign
print(half, list[1], copy.Speed, mid)
  • pairs/ipairs still work; generalized iteration is preferred and iterates arrays in order.
  • #t is only defined for arrays without holes. Setting t[i] = nil in the middle creates holes.
  • Numbers are doubles: integers are exact to 2^53. Use math.floor/// for integer math.
  • tick() is discouraged: use os.clock() for benchmarks, workspace:GetServerTimeNow() for synced time across clients/server, os.time()/DateTime.now() for wall-clock UTC.

Types (new type solver)

Roblox's new type solver is generally available (Nov 2025) and Workspace.UseNewLuauTypeSolver controls it; Workspace.LuauTypeCheckMode sets the default mode. Write code that checks cleanly under --!strict.

--!strict
export type Item = {
	id: string,
	name: string,
	stack: number,
	tags: { string }?, -- optional field
}

type Rarity = "Common" | "Rare" | "Legendary" -- singleton (literal) union

local function totalStack(items: { Item }, filter: ((Item) -> boolean)?): number
	local total = 0
	for _, item in items do
		if filter == nil or filter(item) then
			total += item.stack
		end
	end
	return total
end

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

local rarity: Rarity = "Rare"
local part = workspace:FindFirstChild("Door") :: BasePart? -- cast with ::
print(totalStack({}, nil), first({ 1, 2 }), rarity, part)
  • Refine before use: if part then ... end, if typeof(x) == "Instance" and x:IsA("BasePart") then.
  • typeof(v) knows Roblox types ("Vector3", "Instance", ...); type(v) only knows Lua types.
  • Instance types come from IsA refinement or FindFirstChildOfClass/FindFirstChildWhichIsA.
  • Avoid any except at trust boundaries (remote payloads, decoded JSON) — validate there, then narrow.

Details: generics, type packs, read/write properties, user-defined type functions, typing OOP classes, and migrating legacy code to strict: references/type-system.md.

Modules and require

ModuleScripts run once per environment (server or each client) and return one value, cached for every later require. A module in ReplicatedStorage required by both sides runs twice, once per side, with separate state.

local ReplicatedStorage = game:GetService("ReplicatedStorage")

local Signal = require(ReplicatedStorage.Packages.Signal) -- instance path (always works)
local Config = require("./Config") -- require-by-string: sibling of this script (script.Parent)
local Utils = require("@self/Utils") -- child of this script
local Shared = require("@game/ReplicatedStorage/Shared") -- absolute from game
print(Signal, Config, Utils, Shared)
  • String paths: ./ = script.Parent, ../ = script.Parent.Parent, @self/ = script, @game/ = game. They do not wait for instances to exist. Use instance paths with WaitForChild when the module may not have replicated yet.
  • Never create circular requires; they deadlock/error. Break cycles with dependency injection or events.
  • Don't yield at module top level (no WaitForChild chains on things that may never exist, no DataStore calls): every requirer blocks until the module returns.

Module / class pattern

--!strict
local Counter = {}
Counter.__index = Counter

export type Counter = typeof(setmetatable({} :: { value: number, step: number }, Counter))

function Counter.new(step: number?): Counter
	return setmetatable({ value = 0, step = step or 1 }, Counter)
end

function Counter.increment(self: Counter): number
	self.value += self.step
	return self.value
end

return Counter

Prefer plain modules of functions + data tables over deep inheritance. Use composition.

Errors and retries

local DataStoreService = game:GetService("DataStoreService")
local store = DataStoreService:GetDataStore("PlayerData")

local function retry<T...>(attempts: number, fn: () -> T...): (boolean, T...)
	local delaySeconds = 1
	for attempt = 1, attempts do
		local result = table.pack(pcall(fn))
		if result[1] then
			return table.unpack(result, 1, result.n)
		end
		if attempt < attempts then
			warn(`attempt {attempt} failed: {result[2]}`)
			task.wait(delaySeconds)
			delaySeconds *= 2 -- exponential backoff
		end
	end
	return false
end

local ok, data = retry(3, function()
	return store:GetAsync("Player_1")
end)
print(ok, data)
  • error("message", 2) blames the caller; error({ code = "X" }) throws a table for structured errors.
  • xpcall(fn, debug.traceback) keeps the stack trace.
  • Don't wrap everything in pcall — only calls that can fail for reasons outside your code.

Concurrency: the task library

CallBehavior
task.spawn(fn, ...)Run now (until first yield) on a new thread.
task.defer(fn, ...)Run at the end of the current resumption cycle. Good for "after this finishes".
task.delay(t, fn, ...)Run after t seconds. Returns a thread you can task.cancel.
task.wait(t?)Yield ≥ t seconds (one frame if omitted); returns elapsed time.
task.cancel(thread)Stop a spawned/delayed thread.
task.desynchronize() / task.synchronize()Parallel Luau (inside Actors) — see roblox-performance.
  • Per-frame work: RunService.Heartbeat (after physics), RunService.PreSimulation/PostSimulation, RunService:BindToRenderStep (client, before render, ordered). Don't while true do task.wait() end for per-frame logic; connect to an event and use its deltaTime.
  • Write handlers that work under deferred signal behavior (Workspace.SignalBehavior): handlers run later in the frame, not inline. New template places use Deferred, Default will switch to it, and server authority requires it. Don't rely on handler side effects being visible right after :Fire().
  • Never yield inside metamethods or Changed-style callbacks that must be synchronous.

Cleanup (memory leaks are the #1 Roblox bug)

local Players = game:GetService("Players")

local connections: { RBXScriptConnection } = {}

table.insert(
	connections,
	Players.PlayerRemoving:Connect(function(player)
		print(`{player.Name} left`)
	end)
)

local function cleanup()
	for _, connection in connections do
		connection:Disconnect()
	end
	table.clear(connections)
end

cleanup()
  • Instance:Destroy() disconnects that instance's connections and locks Parent; it does not clear your Lua references — nil out tables that hold players/instances on PlayerRemoving.
  • Use :Once() for one-shot connections. Use a Trove/Janitor/Maid-style helper for groups.
  • Keys that are Player/Instance objects in long-lived tables leak unless removed on leave/destroy.

Buffers and vectors (performance-sensitive data)

local packet = buffer.create(10)
buffer.writeu16(packet, 0, 513) -- item id
buffer.writef32(packet, 2, 12.5) -- amount
buffer.writei32(packet, 6, -4)
print(buffer.readu16(packet, 0), buffer.readf32(packet, 2), buffer.len(packet))

local v = vector.create(1, 2, 3) -- native 3-float vector (same type as Vector3 at runtime)
print(vector.magnitude(v), vector.normalize(v), vector.dot(v, vector.one))

buffer is the right tool for compact network payloads, large grids, and binary serialization. Standard-library quick reference: references/stdlib-cheatsheet.md.

Legacy → current (most common mistakes)

Don't writeWrite instead
wait(), spawn(), delay()task.wait(), task.spawn(), task.delay()
game.Workspace, game.Playersworkspace, game:GetService("Players")
Instance.new("Part", parent)create, set properties, then set .Parent last
Humanoid:LoadAnimation()Animator:LoadAnimation()
Model:SetPrimaryPartCFrame()Model:PivotTo() / Model:GetPivot()
workspace:FindPartOnRay(Ray.new(...))workspace:Raycast(origin, direction, params)
BodyVelocity, BodyPosition, BodyGyroLinearVelocity, AlignPosition, AlignOrientation
player:LoadCharacter()player:LoadCharacterAsync()
MarketplaceService:GetProductInfo():GetProductInfoAsync()
BadgeService:AwardBadge():AwardBadgeAsync()
player:GetRankInGroup()GroupService:GetRolesInGroupAsync() / player:IsInGroupAsync()
TeleportService:Teleport*() variantsTeleportService:TeleportAsync()
Chat service / ChatService modulesTextChatService
GuiObject.DraggableUIDragDetector
table.getn, table.foreach, getfenv#t, for ... in, nothing

The full deprecation map lives in the roblox-code-review skill.

Performance idioms (only where it matters)

  • Measure first (debug.profilebegin/profileend, MicroProfiler, Script Profiler).
  • --!native at the top of a script, or @native on a function, compiles hot numeric code natively. Use it on math-heavy modules, not everywhere (compile time + memory limits). Type annotations help it.
  • Cache Instance lookups and service references outside loops; avoid FindFirstChild chains per frame.
  • Build large strings with table.concat or buffer, not repeated .. in loops.
  • Reuse RaycastParams/OverlapParams objects instead of creating them per call.

Related skills

roblox-architecture (project structure, client/server), roblox-networking (remotes), roblox-performance (profiling, Parallel Luau), roblox-code-review (review checklist, full deprecation map).

FAQ

What does the roblox-luau skill do?

Modern, type-safe Luau for Roblox, covering --!strict and the new type solver, generics, string interpolation, if-expressions, task library, require-by-string, module/OOP patterns, error handling with retries, buffer/vector, and legacy APIs to avoid. Use when writing, refactoring, or explaining any Roblox script, fixing type errors, or modernizing old Lua code.

How do I install roblox-luau?

Run npx skills add https://github.com/el4cteo/roblox-skills --skill roblox-luau in your project. The skills CLI detects the agents you use and copies the skill into the right folder. For Claude Code you can also place the skill folder at ~/.claude/skills/roblox-luau/ (personal) or .claude/skills/roblox-luau/ (project).

Which AI agents can use roblox-luau?

Any agent that supports the open Agent Skills format (a folder with a SKILL.md file), including Claude Code, Codex, Cursor, GitHub Copilot, Gemini CLI, Windsurf, Cline, OpenCode and more.

Is roblox-luau safe to install?

No partner security audit is available yet. Skills can run commands and scripts, so read the SKILL.md and any bundled files before installing.

Who maintains roblox-luau?

It is published by el4cteo in el4cteo/roblox-skills. It has 474 recorded installs.

More from el4cteo/roblox-skills

View all →

roblox-world-building

el4cteo/roblox-skills

Building worlds with code - parts, models and pivots, materials, Lighting (LightingStyle, Atmosphere, post-processing), day/night, Terrain API, in-game CSG and destruction (GeometryService Subtract/Union/Fragment), ProceduralModel, EditableMesh/EditableImage, particles/beams/trails/highlights. Use when creating maps, environments, lighting, destructible objects, procedural levels, or visual effects.

Backend & APIs487 installs

roblox-ui

el4cteo/roblox-skills

Responsive cross-platform UI - ScreenGui and safe areas (ScreenInsets), Scale vs Offset, UIListLayout flex/UIFlexItem, constraints, StyleSheets/tokens/themes, RichText, gamepad/touch navigation, UIDragDetector, CanvasGroup, tweens, BillboardGui/SurfaceGui, React-lua/Fusion/Vide. Use when building HUDs, menus, shops, inventories, or fixing UI that breaks on mobile, console, or other screen sizes.

Frontend & Design482 installs

roblox-testing

el4cteo/roblox-skills

Open the skill to read its SKILL.md instructions.

Testing & QA469 installs

roblox-code-review

el4cteo/roblox-skills

Review Roblox/Luau code for exploits, data loss, memory leaks, performance problems, lifecycle bugs, policy issues, and deprecated or superseded APIs, using a severity-ranked checklist and the full 2026 deprecation map. Use when reviewing a PR or script, auditing a game before launch, modernizing legacy code, or asked what is wrong with some Roblox code.

roblox-performance

el4cteo/roblox-skills

Open the skill to read its SKILL.md instructions.

roblox-game-systems

el4cteo/roblox-skills

Open the skill to read its SKILL.md instructions.

Media & Creative460 installs

Popular code quality & workflow skills

View category →

grill-me

mattpocock/skills

A relentless interview to sharpen a plan or design.

Scan a codebase for deepening opportunities, present them as a visual HTML report, then grill through whichever one you pick.

handoff

mattpocock/skills

Compact the current conversation into a handoff document for another agent to pick up.

domain-modeling

mattpocock/skills

Build and sharpen a project's domain model. Use when discussing codebase terminology, writing or editing a GLOSSARY.md, or recording or editing an ADR.

codebase-design

mattpocock/skills

Shared vocabulary for designing deep modules. Use when the user wants to design or improve a module's interface, find deepening opportunities, decide where a seam goes, make code more testable or AI-navigable, or when another skill needs the deep-module vocabulary.

diagnosing-bugs

mattpocock/skills

Diagnosis loop for hard bugs and performance regressions. Use when the user says "diagnose"/"debug this", or reports something broken/throwing/failing/slow.