Roblox Remote Events & Functions
RemoteEvent vs RemoteFunction
| Type | Direction | Returns value? | Use when |
|---|---|---|---|
RemoteEvent | Any direction | No (fire-and-forget) | Notifying server of player action, broadcasting state |
RemoteFunction | Client→Server | Yes (yields caller) | Client needs a result back (e.g. fetch inventory) |
UnreliableRemoteEvent | Any direction | No | High-frequency updates where dropped packets are fine |
Default to RemoteEvent. Avoid server→client RemoteFunction — an exploiter's frozen callback stalls your server thread indefinitely.
Where to Put Remotes
Always store Remotes in ReplicatedStorage. Create them from a server Script that runs before any LocalScript.
ReplicatedStorage/ Remotes/ DealDamage (RemoteEvent) GetInventory (RemoteFunction) SyncPosition (UnreliableRemoteEvent)lua
-- Script in ServerScriptServicelocal folder = Instance.new("Folder")folder.Name = "Remotes"folder.Parent = game:GetService("ReplicatedStorage")
local function make(class, name) local r = Instance.new(class) r.Name = name r.Parent = folder return rend
make("RemoteEvent", "DealDamage")make("RemoteFunction", "GetInventory")make("UnreliableRemoteEvent", "SyncPosition")Firing Patterns
Client → Server (FireServer)
lua
-- LocalScriptlocal DealDamage = game:GetService("ReplicatedStorage").Remotes:WaitForChild("DealDamage")DealDamage:FireServer({ targetId = 12345, amount = 50 })-- First arg on server is always the firing Player (injected automatically, cannot be spoofed)lua
-- Script (server) — VALIDATE everything in the payloadDealDamage.OnServerEvent:Connect(function(player, data) -- player identity is trustworthy; data contents are notend)Server → One Client
lua
local Notify = game:GetService("ReplicatedStorage").Remotes:WaitForChild("Notify")Notify:FireClient(player, { message = "Welcome!" })lua
-- LocalScriptNotify.OnClientEvent:Connect(function(data) print(data.message)end)Server → All Clients
lua
AnnounceEvent:FireAllClients({ text = "Game starting in 10 seconds!" })RemoteFunction (Client Calls, Server Returns)
lua
-- Script (server)GetInventory.OnServerInvoke = function(player) return getPlayerInventory(player.UserId)endlua
-- LocalScriptlocal inventory = GetInventory:InvokeServer() -- yields until server returnsUnreliableRemoteEvent (High-Frequency Sync)
lua
-- LocalScriptRunService.Heartbeat:Connect(function() SyncPosition:FireServer(character.HumanoidRootPart.CFrame)end)lua
-- Script (server) — still validateSyncPosition.OnServerEvent:Connect(function(player, cframe) if typeof(cframe) ~= "CFrame" then return end -- apply with sanity bounds checkend)CRITICAL: Server-Side Security
The client is hostile. Treat every argument as untrusted input.
lua
local MAX_DAMAGE = 100local COOLDOWNS = {}local COOLDOWN_SECONDS = 0.5
DealDamage.OnServerEvent:Connect(function(player, data) -- 1. Rate limit local now = tick() if COOLDOWNS[player.UserId] and now - COOLDOWNS[player.UserId] < COOLDOWN_SECONDS then return end COOLDOWNS[player.UserId] = now
-- 2. Type checks if type(data) ~= "table" then return end if type(data.targetId) ~= "number" then return end if type(data.amount) ~= "number" then return end
-- 3. Range clamp local amount = math.clamp(data.amount, 0, MAX_DAMAGE)
-- 4. Server-side weapon lookup — never trust client-provided Instance local weapon = getEquippedWeapon(player) if not weapon then return end
-- 5. Server-side target lookup local target = getPlayerByUserId(data.targetId) if not target then return end
applyDamage(target, amount, player)end)Exploit Patterns & Defenses
| Exploit | What the attacker does | Defense |
|---|---|---|
| Argument injection | Sends unexpected types to crash handler | Type-check all arguments |
| Damage amplification | Sends amount = math.huge | Clamp to sane maximum |
| Remote spam | Fires thousands of times per second | Per-player cooldown |
| Spoofed target | Sends another player's UserId | Server resolves from its own state |
| Infinite yield | Never returns from OnClientEvent callback | Avoid server→client RemoteFunction |
| Duplicate action | Replays a valid fire to buy twice | Check state / consume token before acting |
Quick Reference
FireServer(args) LocalScript → serverFireClient(player, args) server → one clientFireAllClients(args) server → every clientInvokeServer(args) LocalScript → server, waits for returnOnServerEvent server-side listener for FireServerOnClientEvent client-side listener for FireClient/FireAllClientsOnServerInvoke server-side function assigned for InvokeServerCommon Mistakes
| Mistake | Fix |
|---|---|
OnServerEvent in a LocalScript | Use OnClientEvent on client; OnServerEvent is server-only |
| Remotes in ServerStorage | Move to ReplicatedStorage |
| Trusting payload beyond player identity | Validate every field in the payload |
| Server→client RemoteFunction | Use RemoteEvent; frozen client stalls server thread |
No WaitForChild in LocalScript | Remotes may not exist yet; always use WaitForChild |
Multiple OnServerInvoke assignments | Only the last assignment wins; keep it in one place |
| Firing inside tight loop without throttle | Use UnreliableRemoteEvent or accumulate delta time |


