| name | luau-best-practices |
| description | Use when writing, reviewing, or refactoring Luau code for Roblox - modules, services, controllers, error handling (pcall), memory leaks and connection cleanup, server-authoritative security, input validation, naming conventions, or project organization. Triggers include "best practices", "clean code", "code review", "refactor", "memory leak", "server authority". |
Luau Best Practices
Production-quality patterns for Roblox game development.
Core Principles
- Server Authority - Server owns game state; client is for presentation
- Fail Fast - Validate early, error loudly in development
- Explicit > Implicit - Clear intent beats clever code
- Minimal Surface Area - Expose only what's needed
Code Style
Naming Conventions
type PlayerData = { ... }
local ShopService = {}
local PlayerController = require(...)
local playerCount = 0
local function getPlayerData() end
function ShopService:purchaseItem() end
local MAX_PLAYERS = 50
local DEFAULT_HEALTH = 100
local function _validateInput() end
local _cache = {}
File Organization
Order within a file: --!strict → services/imports → constants → types → module table → private state → private functions → public API → return. Full template in references/code-style.md.
Module Patterns
Service Pattern (Server)
local MyService = {}
local _started = false
function MyService:Start()
assert(not _started, "MyService already started")
_started = true
end
function MyService:Stop()
end
return MyService
Controller Pattern (Client)
local MyController = {}
local _player = game:GetService("Players").LocalPlayer
function MyController:Init()
end
function MyController:Start()
end
return MyController
More patterns (signals, state management, lazy init) in references/patterns.md.
Error Handling
Use pcall for External Calls
local success, result = pcall(function()
return dataStore:GetAsync(key)
end)
if not success then
warn("DataStore failed:", result)
return nil
end
return result
Use assert for programming errors (things that should never happen); use pcall + warn for external calls that legitimately fail. Result types and retry patterns in references/error-handling.md.
Memory Management
Always Disconnect
local connection: RBXScriptConnection
connection = event:Connect(function()
end)
connection:Disconnect()
Use Maids/Janitors
local Maid = require(Packages.Maid)
local maid = Maid.new()
maid:GiveTask(event:Connect(handler))
maid:GiveTask(instance)
maid:GiveTask(function()
end)
maid:Destroy()
Weak tables for caches and other leak prevention patterns in references/memory.md.
Security Best Practices
Server Authority
RemoteEvent.OnServerEvent:Connect(function(player, damage)
target.Health -= damage
end)
RemoteEvent.OnServerEvent:Connect(function(player, targetId)
local target = getValidTarget(player, targetId)
if not target then return end
local damage = calculateDamage(player)
target.Health -= damage
end)
Validate All Input
RemoteFunction.OnServerInvoke = function(player, itemId, quantity)
if typeof(itemId) ~= "string" then return end
if typeof(quantity) ~= "number" then return end
if quantity < 1 or quantity > 99 then return end
if quantity ~= math.floor(quantity) then return end
if not Items[itemId] then return end
if not canAfford(player, itemId, quantity) then return end
return purchaseItem(player, itemId, quantity)
end
Rate limiting and anti-exploit patterns in references/security.md.
Common Anti-Patterns
Avoid
wait(1)
task.wait(1)
spawn(fn)
task.spawn(fn)
delay(1, fn)
task.delay(1, fn)
while true do
if something then break end
task.wait()
end
local s = ""
for i = 1, 1000 do
s = s .. tostring(i)
end
workspace.Folder.SubFolder.Part
local folder = workspace:FindFirstChild("Folder")
local subFolder = folder and folder:FindFirstChild("SubFolder")
local part = subFolder and subFolder:FindFirstChild("Part")
Prefer
for _, v in ipairs(array) do end
for _, v in array do end
local x = if condition then a else b
for _, item in items do
if not item.valid then continue end
process(item)
end
local name = player and player.Character and player.Character.Name
Project Structure
src/
├── Server/
│ ├── init.server.luau # Bootstrap
│ ├── Services/ # Game services
│ │ ├── DataService.luau
│ │ └── CombatService.luau
│ └── Components/ # Server components
├── Client/
│ ├── init.client.luau # Bootstrap
│ ├── Controllers/ # Client controllers
│ └── UI/ # UI components
├── Shared/
│ ├── Types.luau # Shared type definitions
│ ├── Constants.luau # Shared constants
│ └── Util/ # Shared utilities
└── Packages/ # Wally packages
Quick Reference
| Do | Don't |
|---|
task.wait() | wait() |
task.spawn() | spawn() |
task.delay() | delay() |
for _, v in t | for _, v in pairs(t) |
| Validate on server | Trust client data |
| Use types | Use any everywhere |
| Disconnect events | Leave connections dangling |
| Use constants | Magic numbers/strings |
| Early return | Deep nesting |
| Small functions | 200+ line functions |
References
- Code Style Guide - Naming, formatting, organization
- Common Patterns - Services, signals, state management
- Error Handling - pcall, Result types, retries
- Memory Management - Cleanup, leaks, weak tables
- Security - Server authority, validation, anti-exploit