> For the complete documentation index, see [llms.txt](https://docs.overdare.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.overdare.com/manual/script-manual/events-and-communication/remoteevent.md).

# Server-Client Communication

## Overview <a href="#overview" id="overview"></a>

OVERDARE’s world operates based on communication between the server and the client.

<div align="left"><figure><img src="https://2064130887-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FhrvYlLq1mQAq0V0vwPsb%2Fuploads%2Fgit-blob-f1a6c72cbf4ac2db35611cbc1f3d451792c643fb%2FGroup%2011.png?alt=media" alt=""><figcaption></figcaption></figure></div>

* The **server** manages the global state of the game and acts as the **central system** that handles communication with all clients (players).
* The **client** is a **local environment** that runs on an individual player’s device, handling player input, visual effects, UI, and more.

Since the server and client operate independently, in a multiplayer game, **game logic**, **camera control**, and **player input handling** must be implemented and communicated using **RemoteEvent** or **RemoteFunction**.

## Types of Communication <a href="#types-of-communication" id="types-of-communication"></a>

Since the server and client have different functionalities, communication between them requires the use of RemoteEvent or RemoteFunction.

For example, GUI elements like buttons are processed only on the client-side, while game logic must be handled on the server-side. In other words, when a skill button click occurs on the client, it needs to send an event to the server to request that the server processes the skill usage logic.

These objects connect the roles of the server and client, enabling core interactions in multiplayer games.

<table><thead><tr><th width="248">Communication Type</th><th width="88">Sender</th><th width="88">Receiver</th><th width="130">Object</th><th>Example</th></tr></thead><tbody><tr><td>Event sent from the server to all clients</td><td>Server</td><td>Client</td><td>RemoteEvent</td><td>Game Over</td></tr><tr><td>Event sent from the server to a specific client</td><td>Server</td><td>Client</td><td>RemoteEvent</td><td>Display level-up UI on level-up</td></tr><tr><td>Event sent from the client to the server</td><td>Client</td><td>Server</td><td>RemoteEvent</td><td>Skill button click</td></tr><tr><td>Request sent from the client to the server, with a result returned</td><td>Client</td><td>Server</td><td>RemoteFunction</td><td>Purchase result and remaining balance</td></tr><tr><td>Request sent from the server to a client, with a result returned (not recommended)</td><td>Server</td><td>Client</td><td>RemoteFunction</td><td>See <strong>Important Notes</strong> below</td></tr></tbody></table>

Clients of different players cannot communicate with each other directly. To pass a value from client A to client B, it has to travel **client A ➡ server ➡ client B**, and the server has to validate the value along the way.

## RemoteEvent and RemoteFunction Objects <a href="#remoteevent-and-remotefunction-objects" id="remoteevent-and-remotefunction-objects"></a>

**RemoteEvent** is an object provided to handle events between the server and client, supporting **one-way communication**. The sender does not stop and moves on to the next line right away.

**RemoteFunction** is an object provided to handle requests and responses between the server and client, supporting **two-way communication**. The caller **yields** until the result arrives. Use it when you need the response before deciding what to do next, such as a purchase result or a lookup of data the server holds.

<table><thead><tr><th width="180">Item</th><th width="240">RemoteEvent</th><th>RemoteFunction</th></tr></thead><tbody><tr><td>Direction</td><td>One-way (request only)</td><td>Two-way (request and response)</td></tr><tr><td>Sender behavior</td><td>Keeps running without stopping</td><td>Waits until a value is returned</td></tr><tr><td>How to connect</td><td><code>OnServerEvent:Connect(function)</code></td><td><code>OnServerInvoke = function</code> — only one can be assigned</td></tr><tr><td>Return value</td><td>Cannot receive one</td><td>Receives multiple values as a <code>Tuple</code></td></tr><tr><td>Sending to many clients</td><td><code>FireAllClients</code> supported</td><td>Not supported</td></tr></tbody></table>

**Use RemoteEvent for notifications that need no result.** A synchronous call stops the caller, so a long server-side task also delays that client’s input handling and UI.

Both objects must be **accessible from both sides**. To achieve this, they are placed in **ReplicatedStorage**, a storage where the server and client can share data. ReplicatedStorage safely synchronizes objects between the server and client, ensuring that they are accessible in both environments. If you move one into a location only one side can see, such as a server-only storage, scripts on the other side cannot reference it.

<img src="https://2064130887-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FhrvYlLq1mQAq0V0vwPsb%2Fuploads%2Fgit-blob-b6834cd8073dc06839a7fc060ef0191775c3f2e8%2Fremote-placement.png?alt=media" alt="" width="300">

> 💡 Tip. To clearly distinguish whether the object is for communication from the server to the client (Server to Client) or from the client to the server (Client to Server), it is recommended to use **prefixes** such as **S2C\_** for server-to-client communication and **C2S\_** for client-to-server communication. This makes the object’s role intuitive, enhancing code readability and maintainability.

![](https://2064130887-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FhrvYlLq1mQAq0V0vwPsb%2Fuploads%2Fgit-blob-68b559b75020552f202f007bd41a3a43d6717a2a%2Fremote-naming.png?alt=media)

## Communication Using RemoteEvent <a href="#communication-using-remoteevent" id="communication-using-remoteevent"></a>

You can send **arguments** along with events when firing a RemoteEvent. The arguments are passed when calling the FireServer, FireClient, or FireAllClients methods, and the receiving side can receive the data in a callback function.

### FireAllClients (Server ➡ All Client) <a href="#fireallclients-server-all-client" id="fireallclients-server-all-client"></a>

The server sends an event to **all clients**. This is useful for synchronizing global game states or delivering the same information to all players.

**In Script**

```lua
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage") 
local S2C_GameEnd = ReplicatedStorage:WaitForChild("S2C_GameEnd")

local function TimeOver()
    local isWin = false
    S2C_GameEnd:FireAllClients(isWin) -- Passing arguments
end
```

**In LocalScript**

```lua
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage") 
local S2C_GameEnd = ReplicatedStorage:WaitForChild("S2C_GameEnd")

local function OnGameEnd(isWin)
    print("[OnGameEnd] ", Players.LocalPlayer.Name, " / isWin : ", isWin)
end
S2C_GameEnd.OnClientEvent:Connect(OnGameEnd)
```

### FireClient (Server ➡ Specific Client) <a href="#fireclient-server-specific-client" id="fireclient-server-specific-client"></a>

This method sends an event from the server to a **specific client**. It is used when handling tasks related to an individual player.

**In Script**

```lua
local Players = game:GetService("Players")

local ReplicatedStorage = game:GetService("ReplicatedStorage") 
local S2C_LevelUp = ReplicatedStorage:WaitForChild("S2C_LevelUp")

local function LevelUp(player)
    local prevLevel = 1
    local curLevel = 2
    S2C_LevelUp:FireClient(player, prevLevel, curLevel) -- Passing arguments
end
```

**In LocalScript**

```lua
local Players = game:GetService("Players")

local ReplicatedStorage = game:GetService("ReplicatedStorage") 
local S2C_LevelUp = ReplicatedStorage:WaitForChild("S2C_LevelUp")

local function OnLevelUp(prevLevel, curLevel)
    print("[OnLevelUp] ", Players.LocalPlayer.Name, " / LevelUp : ", prevLevel, " -> ", curLevel)
end
S2C_LevelUp.OnClientEvent:Connect(OnLevelUp)
```

### FireServer (Client ➡ Server) <a href="#fireserver-client-server" id="fireserver-client-server"></a>

This method sends an event from the client to the **server**. It is used when the server needs to handle the user’s input or specific events (e.g., button clicks, skill use requests).

**In LocalScript**

```lua
local ReplicatedStorage = game:GetService("ReplicatedStorage") 
local C2S_UseSkill = ReplicatedStorage:WaitForChild("C2S_UseSkill")

local function ClickSkillButton()
    local skillID = 1
    C2S_UseSkill:FireServer(skillID)
end
```

**In Script**

```lua
local ReplicatedStorage = game:GetService("ReplicatedStorage") 
local C2S_UseSkill = ReplicatedStorage:WaitForChild("C2S_UseSkill")

local function OnUseSkill(player, skillID)
    print("[OnUseSkill] ", player.Name, " / skillID : ", skillID)
end
C2S_UseSkill.OnServerEvent:Connect(OnUseSkill)
```

## Communication Using RemoteFunction <a href="#communication-using-remotefunction" id="communication-using-remotefunction"></a>

A server Script **assigns** a callback to `OnServerInvoke`, and a client LocalScript calls it with the `InvokeServer` method. The **arguments** passed in the call arrive **starting from the second parameter** of the callback, and whatever the callback returns goes back to the client.

### InvokeServer (Client ➡ Server ➡ Client) <a href="#invokeserver-client-server-client" id="invokeserver-client-server-client"></a>

**In Script (handling side)**

```lua
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local C2S_BuyItem = ReplicatedStorage:WaitForChild("C2S_BuyItem")

local function OnBuyItem(player, itemId, amount)
    -- player is determined by the server from the request itself, so it can be trusted
    print("[BuyItem] ", player.Name, " / itemId : ", itemId, " / amount : ", amount)

    local isSuccess, balance = Purchase(player, itemId, amount)
    return isSuccess, balance -- Returning multiple values at once
end
C2S_BuyItem.OnServerInvoke = OnBuyItem -- Assign the function instead of using Connect
```

**In LocalScript (calling side)**

```lua
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local C2S_BuyItem = ReplicatedStorage:WaitForChild("C2S_BuyItem")

local function ClickBuyButton(itemId, amount)
    -- Waits on this line until the server responds
    local isSuccess, balance = C2S_BuyItem:InvokeServer(itemId, amount) -- Passing arguments

    if isSuccess then
        print("[BuyItem] Success / balance : ", balance)
    else
        print("[BuyItem] Failed")
    end
end
```

**The first parameter of `OnServerInvoke`, `player`, is not a value sent by the client.** The server determines it from the request itself, so you can rely on it for permission checks. Everything after it is **sent by the client** and cannot be trusted.

### Callback Rules for OnServerInvoke <a href="#callback-rules-for-onserverinvoke" id="callback-rules-for-onserverinvoke"></a>

`OnServerInvoke` is not an event but a **property that holds a function**. It works differently from `OnServerEvent`, so keep these three points in mind.

* **You cannot use `:Connect()`.** Assign it as `OnServerInvoke = function`.
* **Only one function is kept.** If you assign more than once, only the last assigned function runs.
* **If you omit `return`, the caller receives `nil`.** Using that value directly can cause an error later in the code.

Overlapping calls each run the callback separately. If you replace `OnServerInvoke` while a call is in progress, **that call finishes with the previous function**, and only new calls made afterward use the new one.

## Server-Side Validation <a href="#server-side-validation" id="server-side-validation"></a>

The engine passes values sent by the client to the callback **without inspecting them.** Checking the type and the range is entirely up to your script. **This applies equally to `OnServerInvoke` of a RemoteFunction and `OnServerEvent` of a RemoteEvent.**

Check the following three steps in order.

1. **Permission** — Confirm that this `player` is entitled to make this request right now. (Distance, state, ownership, cooldown)
2. **Type and structure** — Check the actual type with `typeof()`. A client can send a **forged table that looks like an Instance**, so filter with `typeof(item) == "Instance"`.
3. **Value range** — Check the minimum and maximum. `NaN` passes range checks because every comparison against it is `false`, and `Inf` passes unless you check for an upper bound.

```lua
local function OnBuyItem(player, item, amount)
    -- 1. Permission
    if not CanPurchase(player) then
        return false
    end

    -- 2. Type and structure (defend against forged tables)
    if typeof(item) ~= "Instance" or not item:IsA("Tool") then
        return false
    end

    -- 3. Value range
    if typeof(amount) ~= "number" then
        return false
    end
    if amount ~= amount then -- NaN : differs even from itself
        return false
    end
    if math.abs(amount) == math.huge then -- Inf
        return false
    end
    if amount < 1 or amount > 99 then
        return false
    end

    return Purchase(player, item, amount)
end
C2S_BuyItem.OnServerInvoke = OnBuyItem
```

* **Limit the call interval and count per player** to block excessive requests.
* Do not rely on client-side validation alone. Client code can be tampered with, so server-side validation is always required.
* Do not accept values from the client that the server already knows. (For example, price or ownership)

## Important Notes <a href="#important-notes" id="important-notes"></a>

### The Call Never Resumes Without a Callback <a href="#the-call-never-resumes-without-a-callback" id="the-call-never-resumes-without-a-callback"></a>

If you call a RemoteFunction whose `OnServerInvoke` has not been assigned, **the calling thread stops forever with no error and no timeout.** There is no way to cancel the wait.

* Design your initialization order so the server assigns the callback before any client call is made.
* If the work can take a long time, consider sending the request with a RemoteEvent and reporting the result through a separate event when it finishes.

**You can tell a hang and an error apart by the symptom.** Assigning a value that is not a function (a module table, for example) **passes silently**, and the error appears the moment you call it. In other words, if nothing happens at all, the callback is **not assigned**; if an error appears right at the call, a **non-function value was assigned**.

### Use the Correct Script Type <a href="#use-the-correct-script-type" id="use-the-correct-script-type"></a>

`InvokeServer` can only be called from a LocalScript, and `InvokeClient` only from a Script. Calling from the wrong place is not silently ignored; it raises an error.

### Callback Errors Propagate to the Caller <a href="#callback-errors-propagate-to-the-caller" id="callback-errors-propagate-to-the-caller"></a>

An error raised in the server callback is delivered to the calling client and interrupts the client code. If the work can fail, wrap the call in `pcall()` or return a success flag instead of raising an error.

```lua
local isSuccess, result = pcall(function()
    return C2S_BuyItem:InvokeServer(itemId, amount)
end)

if not isSuccess then
    print("[BuyItem] Error : ", result)
end
```

RemoteEvent has no such propagation. An error in the receiving callback does not affect the sender.

### Do Not Use InvokeClient <a href="#do-not-use-invokeclient" id="do-not-use-invokeclient"></a>

`InvokeClient`, which lets the server call a client, carries three risks, so **its use is strongly discouraged.**

* An error raised on the client **propagates to the server** and interrupts the server code.
* If the target client **disconnects during the call, an error is raised.**
* If the client never returns a value, **the server stops forever.** A malicious client can trigger this on purpose.

If the `player` argument is not valid, the callback does not run and an error is raised at call time. These are the four cases.

* The value is `nil`
* The value is an Instance that is not a Player
* The value is not an Instance (including a table forged to look like a Player)
* The value is a reference to a Player who has already left

Be especially careful when you **store a reference to a player who has left and call with it later.** Check that a stored reference is still under `Players` before using it.

**Alternative** : Send the request with `RemoteEvent:FireClient()`, and if you need a result, have the client send it back through a separate RemoteEvent.

```lua
-- In Script
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local S2C_RequestSetting = ReplicatedStorage:WaitForChild("S2C_RequestSetting")
local C2S_ReplySetting = ReplicatedStorage:WaitForChild("C2S_ReplySetting")

local function RequestSetting(player, settingKey)
    S2C_RequestSetting:FireClient(player, settingKey)
end

local function OnReplySetting(player, settingKey, value)
    -- Receive the result through a separate event instead of a return value
    print("[ReplySetting] ", player.Name, " / ", settingKey, " : ", value)
end
C2S_ReplySetting.OnServerEvent:Connect(OnReplySetting)
```

### Values Change When Crossing the Boundary <a href="#values-change-when-crossing-the-boundary" id="values-change-when-crossing-the-boundary"></a>

Values that cross the server-client boundary are converted along the way. **This applies equally to RemoteEvent arguments and to RemoteFunction arguments and return values, and no error or warning is raised.**

* **Functions are not delivered.** Passing one directly results in `nil`, and placing one as a table value **removes that key entirely.** If you need to pass a callback, agree on a string name and map it on the receiving side.
* **Instances that are not replicated**, such as those under `ServerStorage`, disappear the same way.
* **Tables are delivered as copies.** Filling an argument table on the receiving side leaves the sender’s original empty, so receive what you need as a return value. Metatables are not delivered.
* **Use string keys only.** If a table holds both array elements and named fields, the named fields disappear, numeric keys turn into strings, and instances or functions used as keys can no longer be looked up with the original key.

```lua
-- Risky — the callback key disappears, and owner disappears with it
C2S_SomeFunction:InvokeServer({ "Sword", "Bow", owner = "Diva", callback = OnDone })

-- Safe — nest the array one level in, and agree on a name for the callback
C2S_SomeFunction:InvokeServer({ items = { "Sword", "Bow" }, owner = "Diva", callbackId = "OnDone" })
```

### Passing Instances in a Streaming-Enabled World <a href="#passing-instances-in-a-streaming-enabled-world" id="passing-instances-in-a-streaming-enabled-world"></a>

When `StreamingEnabled` is on, an Instance the server created and returned **may not exist on the client yet.** In that case you receive `nil`, and it may never arrive at all.

* Check for `nil` before using the returned value.
* Wait **with a time limit**, as in `WaitForChild(name, timeout)`.
* If necessary, consider loading the area in advance.

```lua
local Part = C2S_CreatePart:InvokeServer(position)

if Part == nil then
    print("[CreatePart] Not streamed yet")
    return
end
```

## Advanced Usage <a href="#advanced-usage" id="advanced-usage"></a>

* For communication that needs no return value, use **RemoteEvent** instead of RemoteFunction. A synchronous call stops the caller.
* Since data sent from the client cannot be trusted, it should always be **validated by the server**. (See **Server-Side Validation** above)
* Send only the necessary information to the server to reduce network load. (Send **only minimal data** from the client)
* Avoid creating too many RemoteEvents or RemoteFunctions. If tasks can be handled in the same context, process them with a single object.
* Create RemoteEvent connections only when necessary, and **disconnect** when finished to avoid memory leaks. (`Disconnect()` function)
* When using a single object to handle multiple tasks, add the first argument (EventType) to indicate the **task type**. (Example of using EventType: PlayerActionType and ActionID)
* Do not perform long waits inside a callback. The calling client waits just as long.
* For communication within the same environment, use BindableEvent or BindableFunction.
