Roblox RemoteEvents and RemoteFunctions Explained: How Client and Server Talk
The moment a button click on one player's screen needs to change the game for everyone, you need a remote. This guide explains RemoteEvents and RemoteFunctions, when to use each, and how to keep them secure.
In this guide
The Client-Server Boundary, in One Picture RemoteEvent: The Fire-and-Forget Message Worked example: a shop button that gives a coin RemoteFunction: The Call That Waits for an Answer Choosing Between Them Security: Never Trust a Remote Message Debugging Remotes: When Nothing Happens Frequently Asked Questions What is the difference between a RemoteEvent and a BindableEvent? Why does my RemoteEvent work in Studio but not in a live game? Are RemoteEvents automatically secure? Related Guides on RBXMods SourcesSooner or later, every creator hits the same wall: a button works in a solo test, then does nothing in a real game — or works for one player and nobody else sees it. The cause is usually the same: the code ran on the wrong side of the client-server boundary. Code can run in two places — each player's device (the client) and the server — and they cannot simply reach into each other. RemoteEvents and RemoteFunctions are the bridges between them.
Key takeaways
- RemoteEvent = one-way, fire-and-forget message. Use it when the sender does not need an answer.
- RemoteFunction = two-way call that waits for a return value. Use it only when the caller genuinely needs an answer before continuing.
- Remotes live in ReplicatedStorage, so both client and server can see them.
- The server must validate every remote input — a client can send anything, so never trust what arrives.
The Client-Server Boundary, in One Picture
Think of a Roblox game as a room with a referee. Each player's device draws the world, handles that player's screen and buttons, and reports what the player does. The server is the referee: it holds the official game state — scores, money, who owns what — and tells every device what changed. A device cannot change official state directly, and one device cannot talk to another directly. If a button should give every player a point, the message must go through the referee.

That is why the three script types from our Lua beginners guide matter. A Script runs on the server and can change official state. A LocalScript runs on one player's device and can only change that player's own screen and input. When a LocalScript needs the server to act — award points, open a door for everyone — it sends a remote message, and a server Script listens for it.
RemoteEvent: The Fire-and-Forget Message
A RemoteEvent sends a message in one direction and does not wait for a reply. The sender fires it and carries on; the receiver's connected function runs when the message arrives. That makes RemoteEvents right for most gameplay communication: actions, announcements and updates where no answer is needed.
| Direction | Sender calls | Receiver connects to |
|---|---|---|
| Client to server | FireServer(...) | OnServerEvent |
| Server to one client | FireClient(player, ...) | OnClientEvent |
| Server to all clients | FireAllClients(...) | OnClientEvent |
To set one up: in the Explorer, hover over ReplicatedStorage, click +, and insert a RemoteEvent. Name it clearly, for example "BuyItemEvent" — a name that says what the message means, not just "RemoteEvent1". Both sides must agree on that name, because they find the object by it.
Worked example: a shop button that gives a coin
Imagine a shop button on the player's screen. Clicking it should ask the server to award a coin. The click is detected by a LocalScript, but the coin must be awarded by the server — otherwise any player could award themselves coins by editing their own device's code.
First, the LocalScript inside the button:
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local buyEvent = ReplicatedStorage:WaitForChild("BuyItemEvent")
script.Parent.MouseButton1Click:Connect(function()
buyEvent:FireServer("Coin")
end)
Then, the server Script in ServerScriptService:
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local buyEvent = ReplicatedStorage:WaitForChild("BuyItemEvent")
buyEvent.OnServerEvent:Connect(function(player, itemName)
if itemName ~= "Coin" then
return
end
local leaderstats = player:FindFirstChild("leaderstats")
if leaderstats and leaderstats:FindFirstChild("Coins") then
leaderstats.Coins.Value = leaderstats.Coins.Value + 1
end
end)
Notice two details beginners miss. First, the server's function receives the player as its first argument automatically — the client does not send it and cannot fake being another player. Second, the server checks the item name before doing anything. That check is the whole security model, covered below.

RemoteFunction: The Call That Waits for an Answer
A RemoteFunction works like a function call across the boundary: the caller invokes it and waits for a return value. The client calls InvokeServer(...), and the server answers through OnServerInvoke. The server can also call a client with InvokeClient, but that can stall the server if the client never answers, so beginners should avoid it.
Use a RemoteFunction only when the caller cannot continue without the answer — "how many coins do I have?", "can I afford this?", "what round is it?". If the caller can continue without an answer, a RemoteEvent is better, because it never makes the game wait.
Worked example: the shop button wants to know the price before showing it. The LocalScript asks, and the server answers from its own price list:
-- LocalScript: ask the server for a price
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local getPrice = ReplicatedStorage:WaitForChild("GetPriceFunction")
local price = getPrice:InvokeServer("Coin")
print("A coin costs", price)
-- Server Script: answer from the server's own data
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local getPrice = ReplicatedStorage:WaitForChild("GetPriceFunction")
local prices = { Coin = 10, Sword = 50 }
getPrice.OnServerInvoke = function(player, itemName)
return prices[itemName]
end
Two warnings come with RemoteFunctions. First, the caller yields — it pauses until the answer arrives — so a never-answered RemoteFunction freezes that script. Always set OnServerInvoke, and keep its work short. Second, a RemoteFunction has exactly one handler (OnServerInvoke is assigned, not connected), so a second assignment replaces the first.
Choosing Between Them
| Question | Answer |
|---|---|
| Does the sender need a value back? | No: RemoteEvent. Yes: RemoteFunction. |
| Should the sender wait? | No: RemoteEvent. Yes, it cannot continue without the answer: RemoteFunction. |
| One sender, many receivers? | RemoteEvent with FireAllClients — for example, announcing a round start to everyone. |
| Same side only (server to server, client to client)? | Neither — use a BindableEvent, which stays on one side of the boundary. |
If you are unsure, start with a RemoteEvent; switch to a RemoteFunction later only if the caller genuinely needs an answer.
Security: Never Trust a Remote Message
This is the most important section, and the one beginners skip. A RemoteEvent does not prove the message is honest: a player can fire any RemoteEvent with any arguments, as many times as they like, whether or not your button exists. Your server code is the only defence, and Roblox's security documentation states the rule plainly: every piece of data sent from a client must be validated by the server before it is used.

Validate in this order, and reject anything that fails:
- Context and permission. Is this player allowed to do this at all? Are they alive, in the right area, on the right team? A shop purchase from a player who is not near the shop should be refused.
- Type and structure. Is the argument the type you expect — a string, a number, a table of the right shape? If your code expects a number and receives text, refuse it rather than trying to use it.
- Value. Is the value sane? A purchase price must match the server's own price list (never the price the client claims), a requested amount must be positive and within limits, and a position must be within the map.
- Rate. Has this player fired this remote far too often? Keep a per-player timestamp on the server and ignore repeats inside a short cooldown — a server-side debounce, not a client-side one, because a client-side cooldown can simply be removed by the sender.
Apply the same thinking to what you send back. Never send server secrets to clients: anything a player should not know (for example, a hidden item's location) must stay server-side until the game reveals it. And never let a remote set a score, money or inventory to a client-supplied number — the server computes the new value from its own rules. Remember: the client requests, the server decides.
Debugging Remotes: When Nothing Happens
Remote bugs are quiet — no crash, just silence. Work through this checklist:
- Is the remote in ReplicatedStorage? A RemoteEvent in ServerScriptService is invisible to clients, and one in StarterGui is invisible to the server. Both sides must be able to see it.
- Do the names match exactly? "BuyItemEvent" and "BuyitemEvent" are different objects. Copy the name, do not retype it, and use WaitForChild so the script waits for the object instead of erroring on a nil.
- Is the handler connected? A RemoteEvent with no OnServerEvent connection receives messages into the void. Add a print at the top of the handler while testing: if it never prints, the message never arrived.
- Are you mixing up the sides? FireServer only works from a LocalScript, and FireClient only works from a server Script. Each does nothing from the wrong side.
If you are still stuck, add prints on both sides: one before firing, one at the top of the handler. The print that never appears shows where the message was lost.
Frequently Asked Questions
What is the difference between a RemoteEvent and a BindableEvent?
A RemoteEvent crosses the client-server boundary; a BindableEvent does not. BindableEvents let scripts on the same side talk to each other — server to server, or client to client. If both scripts run on the server, you do not need a remote at all.
Why does my RemoteEvent work in Studio but not in a live game?
Usually because Studio's solo test blurs the boundary: in a solo test, client and server run in the same session, so mistakes like a LocalScript changing a server value can appear to work. In a live server the boundary is enforced. Test with two players (Test tab, then Players) to catch these before publishing.
Are RemoteEvents automatically secure?
No. They provide communication, not validation. Security comes from your server handler checking permission, type, value and rate before acting on any message.
Related Guides on RBXMods
- Lua scripting for beginners — scripts, LocalScripts and events, the foundations remotes build on.
- Roblox DataStore explained — how the server saves player data safely.
- How to read Studio script errors — debugging with the Output window.
- How to make your first obby — a complete first game to practise remotes in.
RBXMods is not affiliated with or endorsed by Roblox Corporation. Roblox and the Roblox logo are trademarks of Roblox Corporation.
Sources
- Roblox Creator Documentation: Remote events and callbacks — RemoteEvents for one-way communication and RemoteFunctions for two-way communication across the client-server boundary.
- Roblox Creator Documentation: Securing the client-server boundary — validating remote inputs: context and permission, type and structure, and value validation.
- Roblox Creator Documentation: Start learning with tutorials — official scripting tutorials and curriculum paths, including gameplay scripting.