Skip to content
deltascripts.
Blog

Script, LocalScript and ModuleScript: A Small Studio Experiment

Use one shared module and two small callers to see how script type, location and execution context affect a Roblox Studio example.

By deltascriptsco-admin Published 4 min read
Three code containers representing server, client and shared modules

Two scripts can require the same ModuleScript without sharing the same counter. That surprises plenty of beginners because the source file sits in one place in Explorer. The missing detail is where each caller runs.

We’ll use three files in a fresh Studio project. The expected messages show how shared source differs from shared runtime state.

Build this hierarchy

ReplicatedStorage
    LessonCounter        [ModuleScript]
ServerScriptService
    ServerLesson         [Script, RunContext = Server]
StarterPlayer
    StarterPlayerScripts
        ClientLesson     [LocalScript]

Create the file types shown in the hierarchy. Set ServerLesson’s RunContext to Server, and keep one copy of each caller. Otherwise duplicate print calls can obscure what the counter is doing.

ReplicatedStorage makes this module available to both server and client. Use it for code clients are meant to receive; don’t put server secrets there.

Give the module one simple responsibility

Put this in LessonCounter:

local LessonCounter = {}
local count = 0

function LessonCounter.nextValue()
    count += 1
    return count
end

return LessonCounter

The module returns a table with a nextValue function. That function remembers the local count variable and increments it whenever a caller uses it. Placing the file in Explorer doesn’t start it by itself.

Keep the final return. The callers below expect the returned table to contain nextValue. Returning a different value would change what they can call.

Add the server caller

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local module = ReplicatedStorage:WaitForChild("LessonCounter")
local counter = require(module)

print("SERVER first:", counter.nextValue())
print("SERVER second:", counter.nextValue())

Put this caller in ServerLesson. In a fresh run, its two labelled messages should show 1 and 2. The labels save you having to guess which script printed a bare number.

WaitForChild makes the dependency’s name clear. If the caller keeps waiting, check that spelling and the parent before assuming the module is slow to load.

Add the client caller

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local module = ReplicatedStorage:WaitForChild("LessonCounter")
local counter = require(module)

print("CLIENT first:", counter.nextValue())

Put this caller in ClientLesson under StarterPlayerScripts. Use a playtest that starts a player client. Its first value should be 1 in that fresh client environment, rather than 3 after the server’s two calls.

The client message may appear between the server messages or after them. Don’t use their combined ordering as your test. Compare the server’s two calls with each other and the client’s first call with its own starting state.

Interpret the result carefully

Expected results and their scope
MessageExpected valueWhat it demonstrates
SERVER first1The first call uses the server’s module state.
SERVER second2The second call reaches the same state in that environment.
CLIENT first1The client does not inherit the server’s local counter.

Requiring a module again in the same environment returns its existing reference, as Roblox’s module guide explains. Making the source available to both sides doesn’t synchronize an ordinary local variable across them.

That’s the distinction to carry into a real project. If the server and client need to communicate, design that communication explicitly. Giving both callers the same module name doesn’t supply it.

Diagnose a missing message

If the client message is missing, check the LocalScript’s type, location and runtime copy in the player containers. During play, the original starter hierarchy isn’t the whole story.

If a caller waits, inspect the module name and parent. If it errors, start with that caller’s first error and the value the module returned. Also check that Output isn’t hiding the relevant context.

Extra increments usually warrant a search for duplicate callers or other code modifying the same module state. Restore the three-file setup and start a clean playtest before drawing conclusions.

Choose script types deliberately

When reading a larger project, check RunContext as well as file type. A Script isn’t server-only in every possible configuration; LocalScripts and ModuleScripts have their own roles.

Small entry points on each side make startup easier to follow. Let them call clearly named modules, so you can find where the program begins without hunting through scattered copies of similar code.

Primary references

Comments 0

No approved comments yet. Start the discussion.

Every comment is reviewed by a moderator before publication. Enter only a nickname and your comment; no email or account is needed.

Privacy policy