Articles › Game development

Roblox EP3 Server and client: where Script, LocalScript and ModuleScript run

A Roblox game has two sides: the shared server and each player's device. See who sees which change, put code in the right place, and learn why it stops cheats.

Beginner 5 min read October 16, 2026 Your first Roblox game · lesson 3/5

What you will learn
  • The server is the real game everyone shares. What the server changes, everyone sees
  • What a player's device changes on its own, only that device sees. Fine for screens and effects, not for scores or money
  • Anything that decides winning or losing must be decided by the server, because players' devices can be tampered with

In EP2 we put a Script inside each floor. It works, but we never asked where it runs. This episode answers that, because it is what makes multiplayer games behave correctly, and it is the heart of stopping cheaters. Skip it and you get strange bugs like “I can see it, but my friend can’t”.

One game, two sides

When people join your game, Roblox starts one server on Roblox’s machines. Everyone in that room connects to it, and each person also runs a copy of the game on their own device (called the client).

ServerPlayer’s device (client)
How manyone per roomone per player
Jobthe real game; decides everythingdisplay, button presses, camera, sound
Who sees its changeseveryonethat device only
Can players tamper with itnoyes (this is where cheat tools work)

Principle: the server is the truth of the game; each device is a window looking into it.

See it with your own eyes

Studio can simulate a multiplayer game on one machine:

  1. Open your game from EP2
  2. Go to the Test tab, find Clients and Servers, set players to 2, and press Start
  3. Studio opens three windows: one server and two players (Player1, Player2)

Now add player-side code. Stop the test first, then in Explorer open StarterPlayer → StarterPlayerScripts, press + and choose LocalScript:

local baseplate = workspace:WaitForChild("Baseplate")
baseplate.Color = Color3.fromRGB(255, 120, 200)
print("client: painted the floor pink")

Start the 2-player test again. Both player windows show a pink floor, but the server window still shows the old colour. The LocalScript runs on each player’s device, and each device paints its own floor; the server knows nothing about it.

Make the difference clearer by painting only for Player1:

local Players = game:GetService("Players")
local me = Players.LocalPlayer

if me.Name == "Player1" then
	workspace:WaitForChild("Baseplate").Color = Color3.fromRGB(255, 120, 200)
end

Player1 sees pink, Player2 sees the old colour. That is what “only that device sees it” means.

Server (the real game)Script · scores · win or loseeveryone seesPlayer1's deviceLocalScript · screen · cameraonly mePlayer2's deviceLocalScript · screen · cameraonly me
Server changes → everyone sees · a device's own changes → only it sees

Two new words in this code

  • workspace:WaitForChild("Baseplate"): player-side code starts very early, sometimes before the scene has finished loading. Using workspace.Baseplate directly can error with “not found”. WaitForChild waits until the thing arrives. Simple rule: player-side code that refers to things in the scene uses WaitForChild
  • Players.LocalPlayer: the player who owns this device. It only works in player-side code; on the server it is nil, because the server belongs to no player

Three kinds of code, in the right place

KindRuns onPut it in
Scriptthe serverServerScriptService (recommended) or Workspace
LocalScriptthe player’s deviceStarterPlayerScripts, StarterCharacterScripts, StarterGui
ModuleScriptnothing by itself; something must call itReplicatedStorage if both sides use it, ServerStorage if only the server does

A LocalScript in the wrong place, such as Workspace, simply never runs, with no error. Another bug beginners cannot find.

Newer Scripts have a RunContext property in Properties that picks Server or Client wherever the script sits. It is useful in big projects, but at the start, the table above causes less confusion.

ModuleScript: write once, use on both sides

Values many scripts share, such as the lava colour or how long a floor disappears, should not be retyped in every Script. Put them in one ModuleScript.

In ReplicatedStorage, press +, choose ModuleScript, name it Config:

local Config = {}

Config.LAVA_COLOR = Color3.fromRGB(255, 85, 0)
Config.FADE_SECONDS = 1
Config.GONE_SECONDS = 3

return Config

A ModuleScript must return one thing, usually a table holding several values. Any script that wants it uses require:

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Config = require(ReplicatedStorage:WaitForChild("Config"))

print(Config.GONE_SECONDS)  -- 3

Server Scripts and player LocalScripts can both require the same one, because ReplicatedStorage is sent to every device. Want to change the game’s difficulty? Edit one place.

game:GetService("...") is the standard way to get the game’s core services, such as Players and ReplicatedStorage. Use it instead of game.Players; it always returns the right thing.

Why this stops cheating

Say a LocalScript adds coins when a player picks something up. A player with a cheat tool can edit the code on their own device and give themselves a million coins instantly.

The good news: if the game is designed properly, that cheat stays on their device. The server does not believe it, others do not see it, and it is not saved, because the real score is stored and changed on the server.

The rule for the whole series: never trust the player’s device. Scores, money, items, stages cleared, winning and losing: the server always decides.

One exception worth knowing: your own character is moved by your own device, which then tells the server where it is. Roblox does this so walking never lags. As a result, some cheat tools make characters run faster or fly, so serious games have the server check whether movement makes sense. That is beyond beginner level, but know it exists.

What about the code from EP2?

The lava and fading floors are Scripts, so they run on the server. That is correct: every player sees the floor vanish at the same moment, and nobody can stop lava from killing them.

What is not good yet is that they are spread across every part. With 50 lava slabs there are 50 identical Scripts, and every change means 50 edits. EP4 moves them all into one Script in ServerScriptService.

Try it in 10 minutes

  1. Write a LocalScript that paints the floor for Player1 only, run a 2-player test, and see what each window shows
  2. Make the Config ModuleScript, and change FadePad from EP2 to read Config.GONE_SECONDS instead of the number 3
  3. Move the LocalScript into Workspace and test; you will see it does nothing at all

Common mistakes

  • Handling scores or money in a LocalScript, so nobody else sees them and cheating is easy
  • Putting a LocalScript in Workspace, where it silently never runs
  • Player-side code without WaitForChild, erroring only sometimes (fine on fast machines, broken on slow ones)
  • Using Players.LocalPlayer in a server Script and getting nil

Next episode

Ready for a real game. EP4 puts together a complete obby: checkpoints that respawn you at your latest stage, a stage counter on the leaderboard, every lava slab in the game handled by one Script, and a finish line that counts wins.

FAQ

Why not put everything on the server?

Some things must react instantly on the player's device, such as on-screen buttons, the camera, sounds and effects. Asking the server first every time would add enough lag to spoil the fun.

Is a Script in Workspace different from one in ServerScriptService?

Both run on the server. The difference is that Workspace is also sent to players' devices, while ServerScriptService is not; players cannot see the code inside it, which makes it the right home for the game's main code.

Commands and settings were tested on sample systems. Try them on a test machine before production.