Roblox BindableEvent vs RemoteEvent: When to Use Each

Last updated:

Quick answer: A BindableEvent sends a message between two scripts on the same machine (server to server, or client to client), while a RemoteEvent crosses the network between server and client. Use `:Fire()` and `.Event:Connect()` for Bindables, and use a BindableFunction only when you need a return value.

A BindableEvent lets one script tell another script "something happened" without either of them knowing about the other. It only works inside one environment, so server scripts can talk to server scripts, and LocalScripts can talk to LocalScripts. If a message has to cross between the server and a player, you want a RemoteEvent instead. That single distinction answers most of the confusion around the two.

How do BindableEvents work?

A BindableEvent is an Instance. One script calls :Fire(), and every script that connected to .Event runs its handler. Say a round manager wants to announce that a round ended:

-- ServerScriptService/RoundManager (Script)
local ServerStorage = game:GetService("ServerStorage")

local roundEnded = ServerStorage:WaitForChild("RoundEnded") :: BindableEvent

local function finishRound(winnerName: string)
	roundEnded:Fire(winnerName, os.time())
end

And a different script reacts to it:

-- ServerScriptService/RewardService (Script)
local ServerStorage = game:GetService("ServerStorage")

local roundEnded = ServerStorage:WaitForChild("RoundEnded") :: BindableEvent

roundEnded.Event:Connect(function(winnerName: string, finishedAt: number)
	print(winnerName .. " won at " .. finishedAt)
end)

Notice that the event is stored in ServerStorage. Both scripts need to find it, and clients should not see it, so a server-only container is the right place. For a client-side Bindable, put it somewhere the LocalScripts can reach, such as a folder in ReplicatedStorage or the PlayerGui.

When do you need a BindableFunction?

A BindableFunction is a Bindable you can call and get an answer from. One script assigns the OnInvoke callback, another calls :Invoke(), and the caller waits for the result:

-- Script A: provides the answer
local ServerStorage = game:GetService("ServerStorage")
local getCoins = ServerStorage:WaitForChild("GetCoins") :: BindableFunction

getCoins.OnInvoke = function(player: Player): number
	local stats = player:FindFirstChild("leaderstats")
	return stats and stats.Coins.Value or 0
end
-- Script B: asks for it
local ServerStorage = game:GetService("ServerStorage")
local getCoins = ServerStorage:WaitForChild("GetCoins") :: BindableFunction

local amount = getCoins:Invoke(game.Players:GetPlayers()[1])
print(amount)

Three things to remember. First, OnInvoke is a property you assign, not an event you connect, so there is only ever one handler. Second, Invoke yields: if the handler errors or never returns, the caller is stuck. Third, because of that, a plain function in a ModuleScript is usually simpler for this job. If the two scripts can both require a shared module, reach for that first. The ModuleScript guide covers it.

BindableEvent vs RemoteEvent: what is the real difference?

BindableEventRemoteEvent
Crosses client and serverNoYes
Receives a player argument automaticallyNoYes, on the server
Data is copiedYes (tables)Yes (serialised over the network)
Needs validation of argumentsOnly if you do not trust the sender scriptAlways, the client is untrusted
CostCheap, in-processNetwork traffic, rate-limited by bandwidth

The key line in that table is validation. With a RemoteEvent, anything arriving from a client can be forged, so you check every argument. A BindableEvent is entirely your own code talking to your own code, so there is no hostile sender in the middle. If you are new to the network side, start with the RemoteEvent guide and the RemoteFunction guide, then read server-side validation.

A common pattern combines them: a RemoteEvent brings the request from the client to a server script, and that script fires a BindableEvent so other server systems (rewards, analytics, quests) can react without being wired together.

-- Server
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local ServerStorage = game:GetService("ServerStorage")

local openChest = ReplicatedStorage:WaitForChild("OpenChest") :: RemoteEvent
local chestOpened = ServerStorage:WaitForChild("ChestOpened") :: BindableEvent

openChest.OnServerEvent:Connect(function(player, chestId)
	if typeof(chestId) ~= "string" then return end
	-- validate that the chest exists and is in range, then:
	chestOpened:Fire(player, chestId)
end)

What gets copied when you fire a Bindable?

Roblox copies the arguments you pass. Numbers, strings and booleans are trivially fine. Instances are passed by reference, so a part is still the same part. Tables are deep-copied, which means:

If you need to share an object with behaviour, pass an Instance or an ID and look the object up on the receiving end.

Why is my Bindable handler running late?

Roblox has a setting called SignalBehavior that decides whether handlers run immediately when you call Fire or are deferred until the end of the current frame step. New places default to deferred. That is why code immediately after :Fire() may run before the listener does. Do not depend on the order: if the sender needs the result, use a BindableFunction or restructure the code.

Common mistakes

Practice it

Event-driven design clicks faster when you wire it yourself. On Luablox you can practise events, functions and modules in guided exercises, with deeper background in the theory lessons and plenty of reps in the practice problems.

Related guides: RemoteEvents, RemoteFunctions, server-side validation for remotes, and Script vs LocalScript vs ModuleScript.

Frequently asked questions

Can a BindableEvent send data from a client to the server?

No. A BindableEvent only works between scripts running in the same environment. A LocalScript firing a BindableEvent will only reach other LocalScripts on that same player's client. Use a RemoteEvent to cross the client-server boundary.

What is the difference between BindableEvent and BindableFunction?

A BindableEvent is fire-and-forget: Fire does not wait and returns nothing. A BindableFunction yields the caller until the single OnInvoke callback returns, then hands that value back. Pick the event unless you really need an answer.

Do BindableEvents copy the data I pass?

Tables are copied, so changes the listener makes do not affect the sender, and metatables are lost along the way. Instances are passed by reference. Do not rely on identity of a table across a Fire.

Is there something better than a BindableEvent for my own modules?

Often yes. A small Signal class inside a ModuleScript avoids creating Instances, keeps the data intact without copying, and is easier to test. Bindables are still the right tool when scripts need to talk without requiring the same module.

Explore Luau networking concepts in Roblox Studio, covering RemoteEvents, RemoteFunctions, and secure client-server replication.

Related pages