Skip to main content

Remote Events & Functions

Remotes are the bridge between client and server. Almost every meaningful game action — attacking, buying, using abilities, interacting with objects — goes through a remote. Mastering remote usage unlocks essentially everything.

The Two Types

RemoteEvent — fire and forget. No return value. Use for actions where you don't need confirmation.

remoteEvent:FireServer(arg1, arg2) -- you → server
remoteEvent.OnClientEvent:Connect(function(arg1, arg2) -- server → you
-- handle incoming notification
end)

RemoteFunction — synchronous call. Server returns a value. Use for queries and validated actions.

local result = remoteFunction:InvokeServer(arg1, arg2) -- yields until server responds
-- result is whatever the server returned

Getting Remote References

Always use WaitForChild with a timeout — not FindFirstChild. The remote might not exist yet when your script runs:

local RS = game:GetService("ReplicatedStorage")

-- Single remote, with 10s timeout
local attackRemote = RS:WaitForChild("AttackRemote", 10)
if not attackRemote then
warn("AttackRemote not found")
return
end

-- Remote nested in a folder
local shopRemote = RS:WaitForChild("Remotes"):WaitForChild("Shop")

-- Recursive search (when you don't know the path)
local target = RS:FindFirstChild("AbilityRemote", true) -- true = recursive

Firing with Arguments

Arguments can be any Roblox-serializable type: numbers, strings, booleans, Instances, Vectors, CFrames, or tables of these.

-- No arguments (most ability remotes)
abilityRemote:FireServer()

-- Primitive arguments
shopRemote:FireServer("buy", 7832, 2) -- action, itemId, quantity

-- Instance argument (pass a part or character part)
attackRemote:FireServer(enemyHRP) -- HumanoidRootPart of target

-- Table argument
configRemote:FireServer({ speed = 50, mode = "fast" })

-- Mixed
complexRemote:FireServer("action", targetInstance, { extra = true })

Invoking Remote Functions

InvokeServer yields the current thread. Always wrap in task.spawn to avoid blocking:

-- Bad: blocks your entire script until server responds
local data = dataRemote:InvokeServer()

-- Good: run in a separate thread
task.spawn(function()
local success, data = pcall(function()
return dataRemote:InvokeServer("getStats")
end)
if success then
print("Got data:", data)
end
end)

The server can take any amount of time to respond (or never respond if it's broken). Always use pcall around InvokeServer and consider a timeout:

local function invokeWithTimeout(remote, timeout, ...)
local result = nil
local done = false
task.spawn(function()
result = {pcall(remote.InvokeServer, remote, ...)}
done = true
end)
local t = 0
while not done and t < timeout do
t = t + task.wait(0.1)
end
return table.unpack(result or {false, "timeout"})
end

local success, data = invokeWithTimeout(dataRemote, 5, "getMyStats")

Listening to Incoming Server Events

The server can send events to your client. These are often used for game state notifications:

-- Server broadcasting game state
gameStateRemote.OnClientEvent:Connect(function(state, data)
if state == "boss_phase_2" then
print("Boss entered phase 2!")
elseif state == "round_start" then
print("New round:", data.roundNumber)
end
end)

-- Server sending you damage info
damageRemote.OnClientEvent:Connect(function(amount, source, damageType)
print(string.format("Took %d %s damage from %s", amount, damageType, source))
end)

Common Remote Patterns

Pattern 1: Simple Action Remote

No arguments, fires an immediate server action:

remote:FireServer()

Examples: jump, block, dodge, toggle ability

Pattern 2: Target Remote

Passes a target instance (usually HRP):

remote:FireServer(target.Character.HumanoidRootPart)

Examples: attack, heal ally, grab player

Pattern 3: Action + Data Remote

String key selects the action, additional args are data:

remote:FireServer("use", itemName)
remote:FireServer("purchase", itemId, quantity)
remote:FireServer("equip", slotIndex, itemId)

Examples: shop, inventory, ability system

Pattern 4: Event Bus Remote

One remote carries many different event types:

remote:FireServer({ type = "attack", target = hrp, damage = 50 })
remote:FireServer({ type = "move", destination = Vector3.new(0,0,0) })

Pattern 5: Data Query

Remote function returning player state:

local playerData = remote:InvokeServer()
-- returns: { level = 5, coins = 1200, class = "Warrior" }

Argument Discovery

When rSpy shows an argument but you're not sure what it is, experiment:

  1. Unknown number — try common values: 1, 0, -1, a timestamp, a player ID
  2. Unknown string — try the ability/item name you're using, or common patterns: "buy", "sell", "equip", "use"
  3. Unknown instance — it's probably something on your character or the target. Start with HRP, Head, or the whole model

When in doubt, look at the game's LocalScript source to see exactly what it passes.

Server-Side Validation

Servers validate most important remotes. If you fire a shop remote with an invalid item ID, the server ignores or rejects it. Some things the server typically checks:

  • Are you close enough to the NPC/object?
  • Is the item ID valid?
  • Do you have enough currency?
  • Is the cooldown expired?
  • Are you the right team/class?

Understanding what the server validates tells you what you can't bypass via remotes alone — and what you need to approach differently.