skillZs
★ LIVE SKILL TAGS ★
>>> LIVE SKILLS INDEX <<<
* OPEN SOURCE *
NO LOGIN, NO TRACKING
※ REAL INSTALL DATA ※
← back to all skills
el4cteo/roblox-skills100 installs

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.

How do I install this agent skill?

npx skills add https://github.com/el4cteo/roblox-skills --skill roblox-luau
view source ↗

Is this agent skill safe to install?

  • Gen Agent Trust Hubpass

    The skill provides comprehensive documentation and best practices for modern Luau development on the Roblox platform. It focuses on type safety, performance, and proper resource management without any detected security risks.

  • Socketpass

    No alerts

  • Snykpass

    Risk: LOW · No issues

What does this agent skill do?

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).

Add the canonical catalog link to the repository README so users can inspect current installs and available audits. The publishing guide covers the complete discovery path.

<a href="https://skillzs.dev/skills/el4cteo/roblox-skills/roblox-luau">View roblox-luau on skillZs</a>