roblox-best-practices
Framework-agnostic Roblox/Luau coding standards. Use when writing, reviewing, or refactoring any Luau code (Script, LocalScript, ModuleScript) in a Roblox project, or when the user asks to keep best p
Files44
View on GitHubSKILL.mdroblox-best-practices/SKILL.md
GitHubRoblox Game Development Best Practices
Framework-agnostic standards for clean, resource-frugal Luau. They govern how each script is written, not how the project is structured.
Goals, in priority order: correct → secure (server-authoritative) → efficient (CPU/memory/network) → readable → consistent.
Skill version 2.2.0. If this contradicts a newer release, the installed copy is stale; see CHANGELOG.md.
Session Invariants (must survive compaction)
A summary that drops these rules silently downgrades every file written afterwards. Two standing obligations:
- Carry it forward verbatim in any summary, handoff, plan, or task note. It is active instruction, never compressed into "follow the Roblox skill".
- Re-read this file before acting whenever the card's full text is not visible in your context. Never reconstruct these rules from memory — a half-remembered layout rule looks deliberate and is worse than none.
ROBLOX LUAU SKILL - INVARIANT CARD
1 Three sections, this order:
-- // VARIABLES // -- Services > Modules > Objects > Configuration > State Management
-- // FUNCTIONS // -- definitions only (ModuleScript: Private before Public)
-- // INITIALIZATION // -- everything that runs
2 Documentation Comments (Luau Comments) - default style, adapts to the project.
Default block: --[[ ]] above the function, desc > @param > @return.
Moonwave --[=[ ]=] or --- is equally correct when that is the project's style.
- Desc <= 3 lines and <= 250 chars, contract-level, states PURPOSE.
- Desc never names what the body does to get there: no APIs, algorithms,
collaborators, data structures, or code paths.
- Desc carries NO volatile content: no numbers, thresholds, tunable names,
feature names, or anything that needs editing when the body is retuned.
When a detail cannot be avoided, state it at the most general level
that stays true after the body changes.
- Tags use Moonwave syntax: @param <name> <type> -- <description>
and @return <type> -- <description>. Only when they add what the
signature cannot show.
- English preferred as the universal language. No em dashes or double-hyphen
dashes as punctuation (the -- in a tag is a separator, not punctuation).
No emoji.
- IN-BODY COMMENTS: banned in code you write. Make names and
structure say it; put the why in the block above. Never delete
an existing one; propose removal once, Advisory only.
- Self-documenting code outranks commentary everywhere.
- Existing project comment style wins. Recommend this style when the user
asks to restyle; never impose it.
3 Server is authoritative. Validate every remote arg: type, range, ownership, rate.
4 Clean up everything created. Every connection has an owner and a teardown path.
5 No avoidable per-frame garbage. Never poll what has a signal.
Cold paths are exempt, and periodic work on a timer is scheduling.
6 UpdateAsync + backoff. Save on PlayerRemoving. Flush on BindToClose.
7 Re-validate after every yield: player gone? instance dead? session changed?
Only where a yield sits between the check and its use.
8 Never add --!strict unbidden. Never make a [Beta] feature the production default.
9 Reuse before writing: project, then stdlib, then engine API. Search, never
assume - no claim about a file you have not opened. No wrapper or
abstraction without a caller. But brevity has two hard limits:
- It NEVER reduces what was asked for. Short because it does less = failed.
- It NEVER costs readability. One statement per line, descriptive names,
blank lines kept. Less code means less WORK, not less whitespace.
10 User authority outranks this skill. Recommend; never refactor unasked.
Everything below expands these; nothing below overrides them.
Reference Routing
Load only what the task needs. Each reference is self-contained; read the one whose line matches, not the set.
Authoring
- templates.md — writing a new Script/LocalScript/ModuleScript
- section-layout.md — layout detail, subsection contents, Documentation Comment rules, rejected examples
- style-rules.md — naming, deprecated APIs, misremembered APIs,
const, typing opt-in, module hygiene - adaptive-mode.md — existing codebase with its own conventions
- community-libraries.md — ProfileStore, Packet, Trove, Knit, Fusion, ...
- minimal-code.md — about to write a helper that may already exist; keeping code dense
- edge-cases.md — nil, empty, stale, duplicate, reused, or departed state a function will meet
- luau-language.md — language semantics: coercion, table copies,
require, typing depth,vector/buffer/math,task.spawnvstask.defer, deferred events, error handling, time APIs, native codegen
Implementing a known system — read the one file whose domain matches; each gives assembly order and case-specific failure modes.
- cases/data-economy.md — player data, currency, inventory, trading
- cases/monetization.md — Developer Products, passes/subscriptions, gacha/loot boxes
- cases/progression.md — leaderboards, daily rewards, streaks, offline progress
- cases/combat.md — damage/hit validation, abilities and cooldowns, projectiles, NPC/mob AI
- cases/session-flow.md — round/match lifecycle, matchmaking and reserved servers, cross-server events
- cases/world-interaction.md — interactables and prompts, placement/building, pets and followers
- cases/client-infra.md — HUD/state sync, rate limiting and anti-cheat, analytics instrumentation
Deepening a concern
- performance.md — hot loops, memory, network traffic, physics queries and contact detection, rendering, profiling
- device-performance.md — frame budget in ms, low-end and "potato" devices, quality degradation, time-slicing, per-player bandwidth
- patterns/data.md — state ownership, data stores (+ version history), failure policy after the last retry, per-owner locks
- patterns/network.md — remotes, cross-server (MemoryStore, MessagingService, reserved servers), StreamingEnabled
- patterns/lifecycle.md — connection cleanup, character lifecycle (Humanoid vs CCL), object pooling
- patterns/world.md — CollectionService binding and attributes, client input, anti-patterns to reject on sight
- patterns.md — index of the four pattern files above
- security.md — anti-exploit, remote validation depth, movement sanity checks, text filtering
- monetization-policy.md —
ProcessReceipt, Developer Products and passes, PolicyService compliance - server-authority.md — anything touching movement, physics, input, camera, animation timing,
BindToSimulation, network ownership - ui-crossplatform.md — UI construction, cross-platform and accessibility, input device handling
- genres.md — simulator, FPS, obby, RPG, racing, horror, social, tower defense, battlegrounds
Process
- workflow.md — resolving a session-setup decision, supervision behavior, opening a review, the preflight before a non-trivial system
- verification.md — proving a change works: playtests, multi-client sessions, test injection, telemetry, the command-bar VM pitfall
- studio-mcp.md — Studio MCP connection: which tool, what is irreversible, how not to burn tokens
- external-editors.md — the project is edited outside Studio: Script Sync, Rojo, Argon, Azul, and the toolchain around them
- team-workflow.md — git alongside a place file, branch places, who owns which tree, the review gate, shipping
- diagnosis.md — a reported symptom with no file named yet: narrowing, reproducing, and confirming the cause before any fix
- false-positives.md — reviewing code: whether a finding is real, how severe, what not to flag
- review-checklist.md — finishing any task: the completion gate before calling work done
- evaluation-matrix.md — auditing a project on request: scope, evidence, a 1–5 score across security, lifecycle, and performance, and an honest report
- release-readiness.md — shipping or publishing a place: the pre-publish gate
- api-currency.md — whether a newer engine/Luau API is confirmed before relying on it or flagging it missing
- limits-budgets.md — platform ceilings: DataStore size/requests, MemoryStore, messaging, attributes, animation tracks
Lookup files (limits-budgets.md, genres.md, edge-cases.md) are tables: grep the row you need.
User Authority
This skill is guidance, not a mandate — full control always stays with the user:
- The user's explicit instructions override any convention in this skill. If an instruction conflicts with a Non-Negotiable Runtime Rule, state the risk once, briefly, then follow the user's decision.
- Never act unasked on the strength of this skill alone: no unrequested refactors, restructuring, new files, or "while I'm here" cleanups. Recommend; don't act.
- Language: reply in the language the user writes in. Code, identifiers, and Documentation Comments follow the language the project already uses, and English when there is no precedent (card item 2). A request in any language gets the full skill; no rule here depends on the language of the request.
- Invoked with no task ("use best practices", "follow this skill from now on"): acknowledge that the standards are active and stop — no codebase analysis, no setup questions (references/workflow.md).
Session Setup (decide once, then cache)
Five decisions govern every later task. Resolve each once and lazily, at the first task that needs it, then cache it; never re-ask per file. How to resolve each, and how the supervision level modifies it: references/workflow.md.
| Decision | Default when unresolved |
|---|---|
Supervision level — invocation argument ask/bal/go, or !ask/!bal/!go inline |
Balanced; never ask which level the user wants, absence is the answer |
| Default vs Adaptive mode | Default for new files; for edits, match the file being edited and note the assumption |
| Community libraries | None — use the built-in patterns |
| Server Authority | OFF — most places are not server-authoritative, and assuming otherwise produces confidently wrong code |
Environment facts — SignalBehavior, StreamingEnabled, rig type, strictness header |
Assume none of them; each inverts correct guidance between values |
Default applies these conventions as written; Adaptive proposes an adaptation of the project's own style and waits for confirmation (references/adaptive-mode.md). Only style and structure ever adapt, and community libraries win for the concern they own. Never migrate a project to Server Authority on this skill's initiative: it changes how movement, physics, and input work across the whole place.
Review/refactor mode
One severity per finding (Blocker / Correctness / Advisory), after the confidence gate; Advisory is proposed, never forced, and unrelated code is never reformatted. Gate and calibration: references/workflow.md. What not to flag: references/false-positives.md.
A reported symptom with no file named is diagnosed before anything is changed — reaching for the first plausible file produces a change that looks like a fix and leaves the defect in place: references/diagnosis.md.
Environment & Scale
- Detect the project environment first — Studio-native, or a filesystem project synced by Script Sync, Rojo, Argon, or Azul. Which side is the source of truth differs per tool, and assuming wrong overwrites the user's work: references/external-editors.md. Never start, stop, or reconfigure a sync session unasked. On an MCP connection, references/studio-mcp.md applies before any write.
- Verify newer APIs, and state the basis for every engine fact; memory is never presented as fact. Existence comes from the versioned API dump or an in-Studio probe, behaviour from the Engine API Reference. The docs site lags the engine, so a member missing from it is undocumented, not nonexistent. Cite the check, or say "unverified" and name the check that would settle it: references/api-currency.md.
- Maturity tags: [GA] safe as a default · [Beta] opt-in, may change · [Undocumented] shipped, but no reference page — probe its semantics · [Verify] confirm in the target place · [UNVERIFIED] unconfirmed by this skill. Never make a [Beta] feature a production default — offer it, state its status, recommend the stable path.
- Scale the ceremony to the script. Tiny scripts (< ~40 lines) may use the three top-level headers alone; add deeper headers only when needed, never empty placeholders. Pure data/type modules (config tables, item catalogs, shared types) are exempt from the three-section layout.
Script Section Layout
Card items 1–2 hold the layout and Documentation Comment rules. The card omits two things:
- Module requires are ordered by source: ServerScriptService → ServerStorage → ReplicatedStorage → Workspace → script-relative, counting only locations the script can legally reach.
- Function order inside FUNCTIONS and what belongs in each VARIABLES subsection are specified, not free.
Full specification — header hierarchy, subsection contents, description rules with worked rejections, self-documenting code: references/section-layout.md. Annotated templates: references/templates.md.
Language & Style Rules
Those that apply on nearly every task:
- Naming:
PascalCaseservices and module tables ·camelCaselocals, functions, Instance references ·UPPER_SNAKE_CASEConfiguration constants. Module publicsPascalCase, privatescamelCase. - Always
game:GetService(); never direct indexing (theworkspaceglobal is fine). - No deprecated APIs such as
wait/spawn/delay,tick, lowercase:connect,Body*movers,Humanoid:LoadAnimation, orSetPrimaryPartCFrame; full list in references/style-rules.md. Discouraged-but-functional APIs (theInstance.newparent argument,FireAllClients) are not deprecated and are Advisory at most. - Type safety is opt-in. Never add or raise
--!strictunbidden; match what the file or project already declares. - Guard external and yielding calls (
DataStore,MarketplaceService,HttpService,TeleportService) withpcallplus a retry policy and a stated failure policy (references/patterns/data.md). - Reuse before writing, and keep it dense — search the project, the standard library, then the engine API. Brevity never reduces what gets delivered and never costs readability.
- Stay framework-agnostic: bind by tags and attributes, discover by service, assume no folder layout.
Complete set — const bindings, CollectionService binding, circular requires, one responsibility per module, the full deprecation list, and the commonly-misremembered-API table: references/style-rules.md.
Non-Negotiable Runtime Rules
Card items 3-7 are the rules; network budgeting is the one the card leaves out. Full statements, the scope that keeps each from being over-applied, and the domain file behind each: references/runtime-rules.md.
They hold in every mode, level, and library. Before flagging a violation, check the scoped exceptions in references/false-positives.md — each rule has shapes that only look like violations.
Before and After a System
- Before any non-trivial system, settle the five preflight questions in order: which case, which ceilings, which server/client split and owner per fact, whether it already exists, and how it will be proven: references/workflow.md. Movement, input, camera, animation timing, or simulation stepping resolve the Server Authority decision first, since it changes the answers.
- After, before calling any Luau work finished, run the finishing gate in references/review-checklist.md. Read it at the end of the task, not the start.