Appearance
Scripts and Execution
Written: 2026-10-14
Learning objectives
- You can tell Script, LocalScript, and ModuleScript apart.
- You can apply RunContext Legacy, Server, and Client rules.
- You can place each script where it runs.
- You can share code with ModuleScripts and a
startpattern. - You can replace Workspace scripts with tags.
Prerequisites
Three script types
Roblox has three script types. The script locations guide defines all three.
A Script runs on the server or the client, based on location and RunContext. A LocalScript runs only on the client and has no run context. A ModuleScript runs when another script requires it and has no run context.
Script in ServerScriptService
luau
--!strict
local ServerScriptService = game:GetService("ServerScriptService")
local helper = ServerScriptService:WaitForChild("Greeter")
local Greeter = require(helper)
print(Greeter.greet("Mira"))Create a ModuleScript named Greeter in ServerScriptService with a greet function before you play. Get the server container once. Wait for the helper. Require it. Call one function. Expected behavior: Output prints the greeting the module returns.
LocalScript in StarterPlayerScripts
luau
--!strict
local Players = game:GetService("Players")
local player = Players.LocalPlayer
print(player.Name)Get the Players service once. Read the local player. Print the name. Expected behavior: each client prints its own player name.
RunContext container rules
Every new Script starts with Legacy context. Legacy means server-side only. It runs in a server container such as ServerScriptService or Workspace.
Server context is also server-side. It can run in ServerScriptService, Workspace, and ReplicatedStorage. The script locations guide warns against ReplicatedStorage for server code, because clients receive everything stored there.
Client context is client-side. It can run in ReplicatedStorage, StarterPlayerScripts, and StarterCharacterScripts. Starter containers copy the script to each player, so the original and the copy both run. That double run is rarely what you want.
Put one Script with Server context in ServerScriptService and one with Client context in ReplicatedStorage. Put shared and side-specific code in ModuleScripts. This shape comes straight from the script locations guide.
Script in ReplicatedStorage
luau
--!strict
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local shared = ReplicatedStorage:WaitForChild("SharedConfig")
local SharedConfig = require(shared)
print(SharedConfig.title)Set this Script's RunContext to Client. Create a ModuleScript named SharedConfig in ReplicatedStorage with a title field. Get the replicated container once. Wait for the module. Require it. Print one field. Expected behavior: each client prints the shared title once.
LocalScripts follow stricter placement. They run in StarterPlayerScripts, StarterCharacterScripts, StarterGui, and StarterPack. They do not run in ReplicatedStorage. ServerStorage runs no scripts at all, though server code can require ModuleScripts stored there. All of these placement facts come from the script locations guide.
Sharing code with ModuleScripts and start
ModuleScripts hold reusable code. The modules guide shows that require runs a module once and returns the same table on later calls. Each side of the client-server boundary gets its own copy.
Give each ModuleScript a start function. Load every module first. Then call each start function. This order lets modules reference each other safely at start time. The script locations guide recommends exactly this shape.
ModuleScript in ReplicatedStorage
luau
--!strict
local Greeter = {}
function Greeter.greet(name: string): string
return "Hello, " .. name
end
function Greeter.start(): ()
print(Greeter.greet("World"))
end
return GreeterBuild a table. Add a greet function and a start function. Return the table. Expected behavior: nothing prints until a Script requires this module and calls start, then Output prints the greeting.
Script in ServerScriptService
luau
--!strict
local ServerScriptService = game:GetService("ServerScriptService")
local first = ServerScriptService:WaitForChild("Greeter")
local second = ServerScriptService:WaitForChild("Scorer")
local Greeter = require(first)
local Scorer = require(second)
Greeter.start()
Scorer.start()Create ModuleScripts named Greeter and Scorer in ServerScriptService, each with a start function. Load both modules before starting either one. Expected behavior: both systems start once, in the order you call them.
Use tags, not scattered scripts
Avoid one script per part in Workspace. That pattern is hard to debug and hard to reuse. Tag the parts instead. Let one ModuleScript manage every tagged part.
CollectionService tracks tags. GetTagged lists current members. GetInstanceAddedSignal fires when a new member arrives. The script locations guide recommends this tag pattern over attached scripts.
ModuleScript in ServerScriptService
luau
--!strict
local CollectionService = game:GetService("CollectionService")
local Coins = {}
local function setup(coin: Instance): ()
coin:SetAttribute("Touched", true)
end
function Coins.start(): ()
for _, coin in CollectionService:GetTagged("Coin") do
setup(coin)
end
CollectionService:GetInstanceAddedSignal("Coin"):Connect(setup)
end
return CoinsTag two parts with Coin in Studio. Set up current coins first. Then connect future coins. Expected behavior: every part tagged Coin gets the Touched attribute, including parts tagged later.
Common mistakes
Mistake 1: expecting a LocalScript to run in ReplicatedStorage.
Wrong example below. It waits forever in the wrong home.
LocalScript in ReplicatedStorage
luau
--!strict
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local config = ReplicatedStorage:WaitForChild("SharedConfig")
print(config.Name)It fails because LocalScripts do not run in ReplicatedStorage. The script locations guide states this directly. Fix it by using a Script with Client context in ReplicatedStorage, or by moving the LocalScript to StarterPlayerScripts.
Script in ReplicatedStorage
luau
--!strict
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local config = ReplicatedStorage:WaitForChild("SharedConfig")
print(config.Name)Set RunContext to Client. Expected behavior: the client prints the config name.
Mistake 2: leaving RunContext on Legacy in a client container.
Wrong example below. It never runs where you placed it.
Script in StarterPlayerScripts
luau
--!strict
local Players = game:GetService("Players")
local player = Players.LocalPlayer
print(player.Name)It fails because Legacy Scripts do not run in Starter containers. Fix it by setting RunContext to Client, or by switching to a LocalScript in that container.
Mistake 3: attaching behavior scripts to every Workspace part.
Wrong shape below. It copies the same logic across many scripts.
Script in Workspace
luau
--!strict
local Workspace = game:GetService("Workspace")
local part = Workspace:FindFirstChild("Coin1")
if part then
part:SetAttribute("Touched", true)
endIt works once but it scales poorly. Every new coin needs another edit. Fix it with one tagged collection and one ModuleScript start function, as shown above.
Exercises
- Place one server Script and confirm it prints on the server only.
- Share one ModuleScript between server and client containers.
- Convert three per-part scripts into one tag-driven ModuleScript.
Solution 1
Script in ServerScriptService
luau
--!strict
local Workspace = game:GetService("Workspace")
local folder = Instance.new("Folder")
folder.Name = "ServerOnly"
folder.Parent = Workspace
print(folder.Name)Expected behavior: Output prints ServerOnly on the server log.
Solution 2
ModuleScript in ReplicatedStorage
luau
--!strict
local Titles = {}
Titles.label = "Server And Client"
function Titles.start(): ()
print(Titles.label)
end
return TitlesRequire this module from one server Script and one client Script, then call start on each side. Expected behavior: both logs print the same label from separate copies.
Solution 3
ModuleScript in ServerScriptService
luau
--!strict
local CollectionService = game:GetService("CollectionService")
local Gems = {}
local function setup(gem: Instance): ()
gem:SetAttribute("Ready", true)
end
function Gems.start(): ()
for _, gem in CollectionService:GetTagged("Gem") do
setup(gem)
end
CollectionService:GetInstanceAddedSignal("Gem"):Connect(setup)
end
return GemsTag three parts with Gem. Expected behavior: all three parts gain the Ready attribute with one script.
Summary
Scripts need the right home. Legacy runs on the server. Client context runs on the client. Modules carry shared logic with a start call. Tags replace scattered scripts. Next, meet the built-in tools in Services and Events.