Roblox Luau

by gamedev-skillsd4b0e35550c5No license1.3K starsListed Oct 8, 2026Updated Oct 8, 2026Repository updated 11 days ago

Script a Roblox experience in Luau: get services, create and parent Instances, connect events, run server Scripts vs client LocalScripts, and communicate across the client/server boundary with RemoteEvents/RemoteFunctions (server-authoritative). Use when building or debugging Roblox Studio scripts — when the user mentions Roblox, Luau, services, RemoteEvent, Instance.new, PlayerAdded, or client vs server. For saving player data use roblox-datastores.

Instructions onlySoftware Development
AI-generated overview

Guides writing Roblox Luau scripts: services, instances, events, and server/client RemoteEvent communication.

What it does
This skill provides instructions for scripting Roblox experiences in Luau, covering how to obtain services, create and parent Instances, connect events, and decide between server Scripts and client LocalScripts. It shows how to communicate across the client/server boundary with RemoteEvents and RemoteFunctions while keeping the server authoritative, and includes code patterns for leaderstats, instance setup, event connections, per-frame loops, and ModuleScripts. It also lists common pitfalls and points to a reference file on the client/server model.
When to use it
Use it when building or debugging Roblox Studio scripts, such as when a project contains Script, LocalScript, or ModuleScript objects, .rbxl(x) places, or a Rojo project file, or when code calls game:GetService. It is not intended for persisting player data, remote protocol architecture or exploit hardening, generic Lua questions, or engine-agnostic input and save architecture.
Requirements
No scripts are shipped; it is instructions only. It targets the current Roblox engine and Roblox Studio, and references one additional document, references/client-server.md.

Roblox Luau Scripting

Script a Roblox experience in Luau: services, Instances, events, the server/client split, and secure cross-boundary communication. Targets the current Roblox engine and Studio.

When to use

  • Use when writing Roblox scripts: getting services, creating/parenting instances, connecting events, deciding server vs client, or wiring RemoteEvent/ RemoteFunction communication.
  • Use when the project has Script/LocalScript/ModuleScript objects, .rbxl(x) places, or a Rojo *.project.json, and code calls game:GetService(...).

When not to use: persisting data across sessions → roblox-datastores. Remote protocol architecture, exploit hardening, rate limits, high-frequency replication, and multi-client abuse testing → roblox-networking. Generic Lua questions unrelated to the Roblox API. Engine-agnostic input/save architecture → input-systems / save-systems.

Core workflow

  1. Get services with game:GetService("Name"). Common ones: Players, Workspace, ReplicatedStorage (shared client+server), ServerScriptService (server-only code), ServerStorage, RunService, UserInputService (client).
  2. Know where code runs. A Script runs on the server; a LocalScript runs on a client (in StarterPlayerScripts, StarterGui, or the player's character). A ModuleScript is shared code you require.
  3. Create instances deliberately. local p = Instance.new("Part"), set its properties, then set p.Parent last (parenting triggers replication).
  4. React with events. :Connect to signals like Players.PlayerAdded, part.Touched, or RunService.Heartbeat. Disconnect when done to avoid leaks.
  5. Cross the client/server boundary with Remotes — and never trust the client. Clients request via RemoteEvent:FireServer(...); the server validates and applies. The server is authoritative for all game state.
  6. Test in Studio with Play / Play Here / server+client Start; use the Output window and the server/client view toggle to confirm where code ran.

Patterns

1. Server Script: react to players joining (leaderstats)

lua
-- ServerScriptService/Leaderboard.server.luau  (a Script = runs on the server)local Players = game:GetService("Players")
local function onPlayerAdded(player: Player)    local stats = Instance.new("Folder")    stats.Name = "leaderstats"          -- this name makes it show on the leaderboard
    local coins = Instance.new("IntValue")    coins.Name = "Coins"    coins.Value = 0    coins.Parent = stats
    stats.Parent = player               -- parent LASTend
Players.PlayerAdded:Connect(onPlayerAdded)

2. Create and configure an instance

lua
local Workspace = game:GetService("Workspace")
local part = Instance.new("Part")part.Size = Vector3.new(4, 1, 4)part.Position = Vector3.new(0, 10, 0)part.Anchored = true                    -- won't fall under gravitypart.BrickColor = BrickColor.new("Bright blue")part.Parent = Workspace                 -- set Parent last so it replicates once, fully

3. Connect an event (and disconnect to avoid leaks)

lua
local debounce = falselocal connectionconnection = part.Touched:Connect(function(hit: BasePart)    local character = hit.Parent    local humanoid = character and character:FindFirstChildOfClass("Humanoid")    if not humanoid or debounce then return end    debounce = true    humanoid.Health -= 10    task.wait(1)                        -- task.wait, NOT the deprecated wait()    debounce = falseend)
-- Later, when the part is removed or the round ends:-- connection:Disconnect()

4. Client → server with a RemoteEvent (validate on the server!)

lua
-- ReplicatedStorage: create a RemoteEvent named "BuyItem" (in Studio or via code).-- CLIENT (LocalScript): request a purchase. The client can lie — this is only a request.local ReplicatedStorage = game:GetService("ReplicatedStorage")local buyItem = ReplicatedStorage:WaitForChild("BuyItem")  -- wait: may not have replicated yetbuyButton.MouseButton1Click:Connect(function()    buyItem:FireServer("sword")        -- send the item id only; never the price/resultend)
lua
-- SERVER (Script): the ONLY place the transaction is decided.local ReplicatedStorage = game:GetService("ReplicatedStorage")local buyItem = ReplicatedStorage:WaitForChild("BuyItem")local PRICES = { sword = 100, shield = 75 }
buyItem.OnServerEvent:Connect(function(player: Player, itemId)    -- TRUST NOTHING from the client. Validate types and values.    if type(itemId) ~= "string" then return end    local price = PRICES[itemId]    if not price then return end                         -- unknown item    local coins = player.leaderstats.Coins    if coins.Value < price then return end               -- can't afford    coins.Value -= price                                 -- server applies the change    grantItem(player, itemId)end)

5. A per-frame loop with RunService

lua
local RunService = game:GetService("RunService")-- Heartbeat fires every frame AFTER physics; dt is seconds since the last step.RunService.Heartbeat:Connect(function(dt)    spinner.CFrame *= CFrame.Angles(0, math.rad(90) * dt, 0)  -- 90deg/sec, frame-independentend)

6. Shared code in a ModuleScript

lua
-- ReplicatedStorage/GameConfig (a ModuleScript) — usable by server and client.local GameConfig = {}GameConfig.MaxHealth = 100function GameConfig.damageFor(weapon: string): number    return ({ sword = 25, bow = 15 })[weapon] or 0endreturn GameConfig
lua
local GameConfig = require(game:GetService("ReplicatedStorage"):WaitForChild("GameConfig"))print(GameConfig.MaxHealth)

Pitfalls

  • Trusting the client is an exploit → clients can send any arguments to a RemoteEvent/RemoteFunction. Validate every argument's type and range on the server and keep the server authoritative over health, currency, and inventory.
  • LocalScript doesn't run where you put it → LocalScripts run in StarterPlayerScripts, StarterCharacterScripts, StarterGui, or tools — not in Workspace or ServerScriptService. Server Scripts belong in ServerScriptService/Workspace.
  • Deprecated globals → use task.wait/task.spawn/task.delay, not the old wait()/spawn()/delay() (worse scheduling and throttling).
  • Parenting first, then setting properties → set properties first and Parent last so the instance replicates once in its final state.
  • nil on the client right after join → objects stream/replicate over time; use parent:WaitForChild("Name") instead of indexing directly on the client.
  • Connections never disconnected → long-lived :Connect handlers leak and can fire on destroyed objects; store the connection and :Disconnect() (or use Instance:GetAttributeChangedSignal/:Once where appropriate).
  • Using a RemoteFunction where a RemoteEvent fits → RemoteFunction blocks waiting for a return and a malicious/slow client can stall the server; prefer one-way RemoteEvents unless you genuinely need a reply.
  • Assuming the old type checker -> Luau's New Type Solver reached general release (out of Studio Beta) in Nov 2025. nocheck/non-strict places get it automatically; strict-mode places opt in with the UseNewLuauTypeSolver workspace property, and LuauTypeCheckMode sets a place's default checking mode. It runs only at edit time (no runtime cost), so triage type warnings against the solver the place actually has enabled, not older behavior.

References

  • For the full client/server model (replication, RemoteFunction vs RemoteEvent, :WaitForChild timing, BindableEvent for same-context messaging, attributes, CollectionService tags, and :Once/connection cleanup), read references/client-server.md.

Related skills

  • roblox-datastores — persist player data across sessions (server-only).
  • roblox-networking — production remote contracts, server validation, rate limits, replication, streaming, prediction, and multi-client testing.
  • save-systems — engine-agnostic persistence concepts.
  • game-ai / input-systems — portable AI and input patterns to implement in Luau.

Source and attribution

Source:gamedev-skills/awesome-gamedev-agent-skillsinskills/other-engines/roblox-luauat commitd4b0e35

License: No license

Content belongs to its original authors. SourceWeft indexes it from a public repository.

Report or request removal

More from gamedev-skills/awesome-gamedev-agent-skills