---
url: /roblox-fundamentals/scripts-and-execution.md
description: >-
  Script, LocalScript, ModuleScript, and RunContext rules for where each one
  runs.
---

# Scripts and Execution

*Written: {{ $frontmatter.date }}*

## Learning objectives

* You can tell Script, LocalScript, and ModuleScript apart.
* You can apply [RunContext](https://create.roblox.com/docs/en-us/scripting/locations) Legacy, Server, and Client rules.
* You can place each script where it runs.
* You can share code with ModuleScripts and a `start` pattern.
* You can replace Workspace scripts with tags.

## Prerequisites

* [DataModel and Instances](./datamodel-and-instances.md)
* [Modules and Require](../luau-basics/modules-and-require.md)
* [First Script](../getting-started/first-script.md)

## Three script types

Roblox has three script types. The [script locations](https://create.roblox.com/docs/en-us/scripting/locations) guide defines all three.

A Script runs on the server or the client, based on location and [RunContext](https://create.roblox.com/docs/en-us/scripting/locations). 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](https://create.roblox.com/docs/en-us/scripting/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](https://create.roblox.com/docs/en-us/scripting/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](https://create.roblox.com/docs/en-us/scripting/locations) guide.

## Sharing code with ModuleScripts and start

ModuleScripts hold reusable code. The [modules](https://create.roblox.com/docs/en-us/scripting/module) 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](https://create.roblox.com/docs/en-us/scripting/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 Greeter
```

Build 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](https://create.roblox.com/docs/en-us/reference/engine/classes/CollectionService) tracks tags. [GetTagged](https://create.roblox.com/docs/en-us/reference/engine/classes/CollectionService#GetTagged) lists current members. [GetInstanceAddedSignal](https://create.roblox.com/docs/en-us/reference/engine/classes/CollectionService#GetInstanceAddedSignal) fires when a new member arrives. The [script locations](https://create.roblox.com/docs/en-us/scripting/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 Coins
```

Tag 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](https://create.roblox.com/docs/en-us/scripting/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)
end
```

It 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

1. Place one server Script and confirm it prints on the server only.
2. Share one ModuleScript between server and client containers.
3. Convert three per-part scripts into one tag-driven ModuleScript.

::: details 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.
:::

::: details Solution 2
**ModuleScript in ReplicatedStorage**

```luau
--!strict
local Titles = {}
Titles.label = "Server And Client"

function Titles.start(): ()
	print(Titles.label)
end

return Titles
```

Require 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.
:::

::: details 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 Gems
```

Tag 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](./services-and-events.md).
