Script vs LocalScript vs ModuleScript: Which One and Where?

Last updated:

Quick answer: Use a Script for game logic the server must own (damage, coins, saving), a LocalScript for what one player sees and controls (input, camera, UI), and a ModuleScript to share code between them via require() — and remember that a LocalScript in Workspace or ReplicatedStorage silently never runs.

Use a Script for game logic that must be trusted, a LocalScript for anything a single player sees or controls, and a ModuleScript whenever code needs to be shared. Scripts run on the server and affect everyone, LocalScripts run on one player's device and affect only that player, and ModuleScripts run nowhere on their own — they execute only when another script calls require() on them. Picking the wrong one usually produces no error at all: the code simply never runs.

What is the difference between Script, LocalScript, and ModuleScript?

The three script types differ on exactly three axes: where they run, when they start, and whose game they affect.

TypeRuns whereRuns whenAffects
ScriptThe serverAs soon as it is a descendant of the game (e.g. inside ServerScriptService)Every player in that server
LocalScriptOne player's clientOnce it lands in a client-respected container (PlayerScripts, PlayerGui, Backpack, the character)Only that player
ModuleScriptWherever it is requiredOnly when another script calls require() on itWhichever script required it

A Script is the only type whose changes are authoritative: if the server sets a player's coins, that value replicates to every client and is what gets saved. A LocalScript is perfect for reading keyboard input, moving the camera, or animating a menu — but anything it changes stays on that one machine. A ModuleScript is not a "third runtime" at all; it is a packaged chunk of code with a return value, like a function library.

Why does a LocalScript in Workspace or ReplicatedStorage silently do nothing?

Because a LocalScript only starts inside containers the client actually owns. With the default RunContext, those are:

Workspace, ReplicatedStorage, ServerScriptService, and ServerStorage are NOT in that list. A LocalScript sitting in Workspace produces no error, no warning, and no output — its Running property just stays false forever. This is the single most common beginner trap in Roblox, and it looks identical to "my code is broken" even when the code is perfect.

Two side effects catch people out as well. First, a LocalScript cloned into PlayerGui at runtime does start, but a LocalScript cloned into Workspace at runtime still does not. Second, what you see in Studio's Explorer while testing is the server's view mixed with your client's view — a LocalScript that "exists" in Workspace is real, it is just permanently asleep.

Why should damage, coins, and saving live in a server Script?

Because the client is not trustworthy. Any player can run arbitrary client-side code, edit values in their own copy of the game, and fire remote events with whatever arguments they like. If a LocalScript decides "this hit dealt 500 damage" or "this player has 1 million coins", an exploiter simply says the same thing louder.

The robust split looks like this:

Clients talk to that authoritative server through RemoteEvents, and the server clamps and checks every incoming argument before it changes real state. The same rule covers persistence: coin totals that matter must be written by the server, since a client-reported balance is trivially forged. If you are brand new to scripting altogether, start with Roblox scripting for beginners and come back to the type table once your first Script runs.

How does a ModuleScript work with require()?

Calling require() on a ModuleScript executes its body exactly once per environment and returns the single value the module returns — usually a table of functions.

-- ReplicatedStorage/InventoryUtil (ModuleScript)
local InventoryUtil = {}

function InventoryUtil.totalWeight(items)
	local total = 0
	for _, item in items do
		total += item.weight
	end
	return total
end

return InventoryUtil
-- a Script or LocalScript that wants to use it
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local InventoryUtil = require(ReplicatedStorage:WaitForChild("InventoryUtil"))

print(InventoryUtil.totalWeight({ { weight = 3 }, { weight = 7 } })) -- 10

Three rules make modules predictable:

  1. One return value. Return a table (or a function); everything else in the file is local to the module. A module with no return statement gives you nil.
  2. Run once, then cached. The first require() runs the body; later require() calls return the same table instantly. That means module-level variables behave like singletons — a counter at the top of a module is shared by everything on that side that requires it.
  3. Caching is per side. The server has one cached copy and each client has its own. Server and client requiring the same module get two independent tables, never one shared state.

That third rule is what answers "can a Script and a LocalScript share a module?" — they can share the code, and they should for constants and validation helpers, but they cannot share mutable state through it. If the server increments a module counter and a LocalScript reads its own copy, the LocalScript sees its own number and nothing else. For the deeper patterns (frozen config tables, class-style modules with setmetatable), see the ModuleScripts guide.

What does RunContext change?

Every Script and LocalScript has a RunContext property that overrides the legacy placement rules:

RunContext is why old advice and new behaviour sometimes clash. A Script with RunContext set to Client in Workspace is the modern replacement for "a LocalScript that should run no matter where it is cloned", and it is the right tool for effects that are cloned into the world at runtime. The classic guidance still holds for almost every project: put server code in ServerScriptService, put player code under StarterPlayer, and only touch RunContext when placement genuinely cannot express what you need.

Which script type should I use?

Walk this decision tree in order:

When in doubt, default to the server. You can always move pure-presentation code to a LocalScript later; migrating trusted logic away from the client later means rewriting the security model.

Common mistakes

Learn Luau by doing

Choosing the right script type becomes instinct only after you have misplaced one and watched it do nothing. On Luablox you can practise Luau interactively through /theory, drill the tricky bits on /problems, and follow structured Luablox lessons that build from your first Script to full server-authoritative gameplay systems.

Frequently asked questions

Why is my LocalScript not running in Roblox?

Almost always placement: a LocalScript only runs inside PlayerScripts, PlayerGui, Backpack, the character, or their StarterPlayer/StarterGui/StarterPack sources. In Workspace, ReplicatedStorage, or ServerScriptService it stays idle forever with no error. Move it under StarterPlayer.StarterPlayerScripts, or give a Script the RunContext property Client if it must live elsewhere.

Where does a ModuleScript go in Roblox?

Anywhere both consumers can reach: ReplicatedStorage for code shared by server and clients, ServerScriptService for server-only helpers. The ModuleScript itself never runs on its own — it executes the first time another script calls require() on it, so its location just has to be reachable from every script that needs it.

Can a Script and a LocalScript share the same ModuleScript?

They can share the module code and its returned functions, and that is the recommended way to keep constants and validation logic consistent. They cannot share mutable state: require() caches separately on the server and on each client, so both sides get independent tables. Anything both sides must agree on has to travel over a RemoteEvent or RemoteFunction.

Does a ModuleScript run every time I require it?

No. The body runs once per environment — once on the server and once per client — and every later require() returns the cached result instantly. Module-level variables therefore act like singletons within that side of the game.

What is the difference between RunContext Legacy, Server, and Client?

Legacy makes placement decide where a script runs (the classic rules). Server forces the script to run on the server wherever it sits, and Client runs it on every client wherever it sits — including Workspace. Use Client RunContext for effects cloned into the world at runtime, and Legacy everywhere else.

Learn how to share code and architect clean, modular Roblox systems using ModuleScripts and require().

Related pages